From ef43a346bc39a0a94fde5992c66b0864aa368fba Mon Sep 17 00:00:00 2001 From: Umut Date: Sun, 31 Dec 2023 08:46:58 +0300 Subject: [PATCH 01/38] feat: initial bow of artemis --- enemies/sweet/src/gummy_bear.rs | 76 ++++++++----- game/src/combat/components.rs | 39 ++++++- game/src/combat/plugin.rs | 11 +- game/src/combat/systems.rs | 118 ++++++++++++++++----- game/src/core/sets/gameplay.rs | 1 + game/src/enemy/components.rs | 11 +- game/src/enemy/plugin.rs | 2 + game/src/map/components.rs | 5 + game/src/map/plugin.rs | 1 + game/src/physics/layers.rs | 20 +++- game/src/player/components.rs | 6 ++ game/src/player/plugin.rs | 1 + game/src/player/systems.rs | 2 +- game/src/prelude.rs | 1 + game/src/status_effect/components.rs | 4 +- items/greek/src/bow_of_artemis.rs | 153 ++++++++++++++++++++++++++- modes/survival/src/systems.rs | 11 +- players/greek/src/artemis.rs | 3 +- players/greek/src/hades.rs | 3 +- 19 files changed, 391 insertions(+), 77 deletions(-) diff --git a/enemies/sweet/src/gummy_bear.rs b/enemies/sweet/src/gummy_bear.rs index 110317c..bbba91e 100644 --- a/enemies/sweet/src/gummy_bear.rs +++ b/enemies/sweet/src/gummy_bear.rs @@ -58,6 +58,9 @@ pub const INITIAL_HEALTH: f32 = 5.00; /// Initial speed of the enemy. pub const INITIAL_SPEED: f32 = 100.00; +/// Cooldown of applying damage to the player when in contact. +pub const DAMAGE_COOLDOWN: Duration = Duration::from_millis(1000); + /// Spawns the enemy. pub fn spawn( In(position): In, @@ -67,34 +70,49 @@ pub fn spawn( mut counter: ResMut, ) { counter.increment(); - commands.spawn(( - GummyBear, - EnemyBundle { - // Tags - name: Name::new(format!("Enemy {} [Gummy Bear]", counter.get())), - tag: Enemy, - // Properties - damage: Damage(INITIAL_DAMAGE), - health: Health(INITIAL_HEALTH), - speed: Speed(INITIAL_SPEED), - // Combat - remaining_health: RemainingHealth(INITIAL_HEALTH), - // Physics - body: RigidBody::Dynamic, - restitution: Restitution::PERFECTLY_INELASTIC, - position, - collider: GummyBear.collider(), - layers: CollisionLayers::new( - [Layer::Enemy], - [Layer::MapBound, Layer::Enemy, Layer::PlayerHitBox], - ), - // Texture - mesh: MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), - material: materials.add(ColorMaterial::from(Color::RED)), - transform: Transform::from_translation(Vec3::new(position.x, position.y, 1.00)), - ..default() + commands + .spawn(( + // Tag + GummyBear, + // Attack + DamagePlayerOnContact, + Damage(INITIAL_DAMAGE), + DamageCooldown::new(DAMAGE_COOLDOWN), + // Enemy + EnemyBundle { + // Tags + name: Name::new(format!("Enemy {} [Gummy Bear]", counter.get())), + tag: Enemy, + // Properties + health: Health(INITIAL_HEALTH), + speed: Speed(INITIAL_SPEED), + // Combat + remaining_health: RemainingHealth(INITIAL_HEALTH), + // Physics + body: RigidBody::Dynamic, + restitution: Restitution::PERFECTLY_INELASTIC, + position, + collider: GummyBear.collider(), + layers: CollisionLayers::new( + [Layer::Enemy, Layer::DamagePlayer], + [Layer::MapBound, Layer::Enemy, Layer::PlayerHitBox], + ), + // Texture + mesh: MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), + material: materials.add(ColorMaterial::from(Color::RED)), + transform: Transform::from_translation(Vec3::new(position.x, position.y, 1.00)), + ..default() + }, }, - }, - )); + )) + .with_children(|parent| { + parent.spawn(( + Name::new("Hit Box"), + EnemyHitBox, + Sensor, + Collider::ball(SIZE), + CollisionLayers::new([Layer::EnemyHitBox], [Layer::DamageEnemies]), + )); + }); } diff --git a/game/src/combat/components.rs b/game/src/combat/components.rs index 1b48f2b..3d4b178 100644 --- a/game/src/combat/components.rs +++ b/game/src/combat/components.rs @@ -1,11 +1,48 @@ use crate::prelude::*; -/// Tag component for basic attacks. +/// Tag component for attacks. #[derive(Component, Debug, Reflect)] pub struct Attack; +/// Component for cooldown of applying damage. +#[derive(Component, Debug, Deref, DerefMut, Reflect)] +pub struct DamageCooldown { + pub duration: Duration, +} + +impl DamageCooldown { + /// Creates a new damage cooldown. + pub fn new(duration: Duration) -> DamageCooldown { + DamageCooldown { duration } + } +} + + +/// Tag component for projectiles. +#[derive(Component, Debug, Reflect)] +pub struct Projectile; + + +/// Bundle for projectiles. +#[derive(Bundle)] +pub struct ProjectileBundle { + // Tags + pub tag: Projectile, + // Properties + pub damage: Damage, + // Physics + pub body: RigidBody, + pub position: Position, + pub velocity: LinearVelocity, + pub collider: Collider, + pub layers: CollisionLayers, + // Texture + pub mesh: MaterialMesh2dBundle, +} + + /// Component for remaining health. #[derive(Clone, Copy, Component, Debug, Deref, DerefMut, Reflect)] pub struct RemainingHealth(pub f32); diff --git a/game/src/combat/plugin.rs b/game/src/combat/plugin.rs index 0ccb983..46270ec 100644 --- a/game/src/combat/plugin.rs +++ b/game/src/combat/plugin.rs @@ -9,16 +9,17 @@ pub struct CombatPlugin; impl Plugin for CombatPlugin { fn build(&self, app: &mut App) { // Register components. - app.register_type::(); + app.register_type::(); app.register_type::>(); + app.register_type::(); + app.register_type::(); // Add systems. app.add_systems(PreUpdate, cooldown::.in_set(GameplaySystems::Combat)); + app.add_systems(Update, (damage_player, damage_enemies).in_set(GameplaySystems::Combat)); app.add_systems( - Update, - (damage_player_on_contact_with_enemies, player_death) - .chain() - .in_set(GameplaySystems::Combat), + PostUpdate, + (player_death, enemy_death, despawn_projectiles).in_set(GameplaySystems::Combat), ); } } diff --git a/game/src/combat/systems.rs b/game/src/combat/systems.rs index cec7d1f..f6cd629 100644 --- a/game/src/combat/systems.rs +++ b/game/src/combat/systems.rs @@ -1,45 +1,77 @@ use crate::prelude::*; -/// Damages the player for every enemy it's touching. -pub fn damage_player_on_contact_with_enemies( +/// Damages the player. +pub fn damage_player( mut commands: Commands, mut player_query: Query<&mut RemainingHealth, With>, - mut player_hit_box_query: Query>, - enemy_query: Query<&Damage, (Without, With, Without>)>, + player_hit_box_query: Query<&Parent, With>, + player_damage_query: Query< + (Entity, &Damage, Option<&DamageCooldown>), + (With, Without>), + >, mut collision_event_reader: EventReader, ) { - let mut player_remaining_health = match player_query.get_single_mut() { - Ok(query_result) => query_result, - Err(_) => return, - }; - - let player_sensor_entity = match player_hit_box_query.get_single_mut() { - Ok(query_result) => query_result, - Err(_) => return, + let mut apply_damage_if_applicable = |player_hit_box_entity, player_damage_entity| { + let (damaging_entity, damage, damage_cooldown) = + match player_damage_query.get(player_damage_entity) { + Ok(query_result) => query_result, + Err(_) => return, + }; + let remaining_health = match player_hit_box_query.get(player_hit_box_entity) { + Ok(parent) => player_query.get_mut(parent.get()), + Err(_) => return, + }; + if let Ok(mut remaining_health) = remaining_health { + remaining_health.0 -= damage.0; + if let Some(damage_cooldown) = damage_cooldown { + commands + .entity(damaging_entity) + .insert(Cooldown::::new(damage_cooldown.duration)); + } + } }; - for Collision(contact) in collision_event_reader.read() { - if contact.entity1 == player_sensor_entity || contact.entity2 == player_sensor_entity { - let enemy_entity = if contact.entity1 == player_sensor_entity { - contact.entity2 - } else { - contact.entity1 - }; + for Collision(contacts) in collision_event_reader.read().cloned() { + apply_damage_if_applicable(contacts.entity1, contacts.entity2); + apply_damage_if_applicable(contacts.entity2, contacts.entity1); + } +} - let enemy_damage = match enemy_query.get(enemy_entity) { +/// Damages the enemies. +pub fn damage_enemies( + mut commands: Commands, + mut enemy_query: Query<&mut RemainingHealth, With>, + enemy_hit_box_query: Query<&Parent, With>, + enemy_damage_query: Query< + (Entity, &Damage, Option<&DamageCooldown>), + (With, Without>), + >, + mut collision_event_reader: EventReader, +) { + let mut apply_damage_if_applicable = |enemy_hit_box_entity, enemy_damage_entity| { + let (damaging_entity, damage, damage_cooldown) = + match enemy_damage_query.get(enemy_damage_entity) { Ok(query_result) => query_result, - Err(_) => continue, + Err(_) => return, }; - - player_remaining_health.0 -= enemy_damage.0; - - commands - .entity(enemy_entity) - .insert(Cooldown::::new(Timer::from_seconds(1.00, TimerMode::Once))); - - break; + let remaining_health = match enemy_hit_box_query.get(enemy_hit_box_entity) { + Ok(parent) => enemy_query.get_mut(parent.get()), + Err(_) => return, + }; + if let Ok(mut remaining_health) = remaining_health { + remaining_health.0 -= damage.0; + if let Some(damage_cooldown) = damage_cooldown { + commands + .entity(damaging_entity) + .insert(Cooldown::::new(damage_cooldown.duration)); + } } + }; + + for Collision(contacts) in collision_event_reader.read().cloned() { + apply_damage_if_applicable(contacts.entity1, contacts.entity2); + apply_damage_if_applicable(contacts.entity2, contacts.entity1); } } @@ -59,3 +91,31 @@ pub fn player_death( next_game_state.set(GameState::Over); } } + +/// Handles enemy death. +pub fn enemy_death( + mut commands: Commands, + enemy_query: Query<(Entity, &RemainingHealth), With>, +) { + for (enemy_entity, enemy_remaining_health) in enemy_query.iter() { + if enemy_remaining_health.0 <= 0.00 { + commands.entity(enemy_entity).despawn_recursive(); + } + } +} + + +/// Despawns the projectiles on contact. +pub fn despawn_projectiles( + mut commands: Commands, + projectile_query: Query>, + mut collision_started_event_reader: EventReader, +) { + for CollisionStarted(entity1, entity2) in collision_started_event_reader.read().cloned() { + if projectile_query.get(entity1).is_ok() { + commands.entity(entity1).despawn_recursive(); + } else if projectile_query.get(entity2).is_ok() { + commands.entity(entity2).despawn_recursive(); + } + } +} diff --git a/game/src/core/sets/gameplay.rs b/game/src/core/sets/gameplay.rs index 777ff61..fa8b05b 100644 --- a/game/src/core/sets/gameplay.rs +++ b/game/src/core/sets/gameplay.rs @@ -8,6 +8,7 @@ pub enum GameplaySystems { Enemy, GameMode, Input, + Item, Movement, Physics, Player, diff --git a/game/src/enemy/components.rs b/game/src/enemy/components.rs index 8f1bdaf..cb8e82b 100644 --- a/game/src/enemy/components.rs +++ b/game/src/enemy/components.rs @@ -6,6 +6,16 @@ use crate::prelude::*; pub struct Enemy; +/// Tag component for hit boxes of enemies. +#[derive(Component, Debug, Reflect)] +pub struct EnemyHitBox; + + +/// Tag component for entities that apply damage to enemies on contact. +#[derive(Component, Debug, Reflect)] +pub struct DamageEnemiesOnContact; + + /// Bundle for enemies. #[derive(Bundle)] pub struct EnemyBundle { @@ -13,7 +23,6 @@ pub struct EnemyBundle { pub name: Name, pub tag: Enemy, // Properties - pub damage: Damage, pub health: Health, pub speed: Speed, // Combat diff --git a/game/src/enemy/plugin.rs b/game/src/enemy/plugin.rs index f8c730f..277caa0 100644 --- a/game/src/enemy/plugin.rs +++ b/game/src/enemy/plugin.rs @@ -10,6 +10,8 @@ impl Plugin for EnemyPlugin { fn build(&self, app: &mut App) { // Register components. app.register_type::(); + app.register_type::(); + app.register_type::(); // Add systems. app.add_systems( diff --git a/game/src/map/components.rs b/game/src/map/components.rs index 43104da..6a87325 100644 --- a/game/src/map/components.rs +++ b/game/src/map/components.rs @@ -4,3 +4,8 @@ use crate::prelude::*; /// Tag component for the map. #[derive(Component, Debug, Reflect)] pub struct Map; + + +/// Tag component for the invisible walls around the map. +#[derive(Component, Debug, Reflect)] +pub struct MapBound; diff --git a/game/src/map/plugin.rs b/game/src/map/plugin.rs index 0adccb1..6b2d9f8 100644 --- a/game/src/map/plugin.rs +++ b/game/src/map/plugin.rs @@ -13,6 +13,7 @@ impl Plugin for MapPlugin { // Register components. app.register_type::(); + app.register_type::(); // Add systems. app.add_systems(OnEnter(GameState::Won), despawn_map); diff --git a/game/src/physics/layers.rs b/game/src/physics/layers.rs index c5c1111..2e5ffdb 100644 --- a/game/src/physics/layers.rs +++ b/game/src/physics/layers.rs @@ -4,8 +4,26 @@ use crate::prelude::*; /// Physics layers to differentiate collisions. #[derive(Debug, PhysicsLayer, Reflect)] pub enum Layer { + /// Layer for the invisible bounds of the map. MapBound, + /// Layer for the obstacles in the map. + MapObstacle, + + /// Layer for the player. Player, - PlayerHitBox, + /// Layer for the enemies. Enemy, + + /// Layer for the hit box of the player. + PlayerHitBox, + /// Layer for damaging the player. + DamagePlayer, + + /// Layer for the hit box of the enemies. + EnemyHitBox, + /// Layer for damaging the enemies. + DamageEnemies, + + /// Layer for projectiles. + Projectile, } diff --git a/game/src/player/components.rs b/game/src/player/components.rs index 06a2c43..bf01db5 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -11,6 +11,11 @@ pub struct Player; pub struct PlayerHitBox; +/// Tag component for entities that apply damage to the player on contact. +#[derive(Component, Debug, Reflect)] +pub struct DamagePlayerOnContact; + + /// Bundle for players. #[derive(Bundle)] pub struct PlayerBundle { @@ -30,6 +35,7 @@ pub struct PlayerBundle { pub collider: Collider, pub velocity: LinearVelocity, pub layers: CollisionLayers, + pub axes: LockedAxes, // Texture pub mesh: MaterialMesh2dBundle, // Input diff --git a/game/src/player/plugin.rs b/game/src/player/plugin.rs index 402acb7..0699c59 100644 --- a/game/src/player/plugin.rs +++ b/game/src/player/plugin.rs @@ -10,6 +10,7 @@ impl Plugin for PlayerPlugin { fn build(&self, app: &mut App) { // Register components. app.register_type::(); + app.register_type::(); app.register_type::(); // Add systems. diff --git a/game/src/player/systems.rs b/game/src/player/systems.rs index f6a3cf3..c2c3626 100644 --- a/game/src/player/systems.rs +++ b/game/src/player/systems.rs @@ -95,7 +95,7 @@ pub fn dash( } commands.entity(entity).insert(( Dashing { timer: Timer::new(INITIAL_DASH_DURATION, TimerMode::Once) }, - Cooldown::::new(Timer::new(INITIAL_DASH_COOLDOWN, TimerMode::Once)), + Cooldown::::new(INITIAL_DASH_COOLDOWN), )); } } diff --git a/game/src/prelude.rs b/game/src/prelude.rs index 536ac39..43e9a03 100644 --- a/game/src/prelude.rs +++ b/game/src/prelude.rs @@ -108,6 +108,7 @@ pub use { Any, TypeId, }, + cmp::Ordering, fmt::{ self, Debug, diff --git a/game/src/status_effect/components.rs b/game/src/status_effect/components.rs index bdc3208..d9f607a 100644 --- a/game/src/status_effect/components.rs +++ b/game/src/status_effect/components.rs @@ -14,8 +14,8 @@ pub struct Cooldown { impl Cooldown { /// Creates a cooldown. - pub fn new(timer: Timer) -> Cooldown { - Cooldown { timer, phantom: PhantomData } + pub fn new(duration: Duration) -> Cooldown { + Cooldown { timer: Timer::new(duration, TimerMode::Once), phantom: PhantomData } } } diff --git a/items/greek/src/bow_of_artemis.rs b/items/greek/src/bow_of_artemis.rs index 7da05b7..be56c79 100644 --- a/items/greek/src/bow_of_artemis.rs +++ b/items/greek/src/bow_of_artemis.rs @@ -20,12 +20,14 @@ impl Item for BowOfArtemis { ItemInstance::new(self.clone()) } - fn acquire(&self, _world: &mut World) -> Option { - None + fn acquire(&self, world: &mut World) -> Option { + world.run_system_once_with(self.clone(), acquire) } - fn release(&self, _world: &mut World, entity: Option) { - assert!(entity.is_none()); + fn release(&self, world: &mut World, entity: Option) { + if let Some(entity) = entity { + world.run_system_once_with(entity, release); + } } } @@ -41,5 +43,148 @@ impl Plugin for BowOfArtemisPlugin { // Register resources. app.register_type::(); + + // Add systems. + app.add_systems(Update, attack.in_set(GameplaySystems::Item)); + } +} + +/// Cooldown duration for the attack with the item. +pub const ATTACK_COOLDOWN: Duration = Duration::from_millis(600); + +/// Base range of the item. +pub const BASE_RANGE: f32 = 250.00; + +/// Base damage of the item. +pub const BASE_DAMAGE: Damage = Damage(5.00); + +/// Radius of the projectiles of the item. +pub const PROJECTILE_RADIUS: f32 = 3.00; + +/// Base speed for the projectiles of the item. +pub const BASE_PROJECTILE_SPEED: f32 = 250.00; + +/// Acquires the item. +pub fn acquire( + In(item): In, + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + inventory: Res, + player_query: Query>, +) -> Option { + let player_entity = match player_query.get_single() { + Ok(query_result) => query_result, + Err(_) => return None, + }; + + let item = commands + .spawn(( + Name::new(format!("Item {} ({})", inventory.items.len(), item.name().to_string())), + item, + MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(5.00).into()).into(), + material: materials.add(ColorMaterial::from(Color::BLUE)), + transform: Transform::from_translation(Vec3::new(10.00, 0.00, 3.00)), + ..default() + }, + )) + .id(); + + commands.entity(player_entity).add_child(item); + + Some(item) +} + +/// Releases the item. +pub fn release(In(entity): In, mut commands: Commands) { + if let Some(entity) = commands.get_entity(entity) { + entity.despawn_recursive(); + } +} + +/// Attacks with the item. +pub fn attack( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + item_query: Query<(Entity, &GlobalTransform), (With, Without>)>, + enemy_hit_box_query: Query<&Position, With>, + spatial_query: SpatialQuery, +) { + let damage = BASE_DAMAGE; + let projectile_speed = BASE_PROJECTILE_SPEED; + let attack_area = Collider::ball(BASE_RANGE); + for (item_entity, &item_transform) in item_query.iter() { + let item_position = Position(item_transform.translation().xy()); + + let intersections = spatial_query.shape_intersections( + &attack_area, + item_position.xy(), + 0.00, + SpatialQueryFilter::new().with_masks([Layer::EnemyHitBox]), + ); + + let mut enemies_in_range = intersections + .iter() + .filter_map(|&enemy_hit_box_entity| { + enemy_hit_box_query + .get(enemy_hit_box_entity) + .map(|&enemy_hit_box_position| { + (enemy_hit_box_position, enemy_hit_box_position.distance(*item_position)) + }) + .ok() + }) + .collect::>(); + + enemies_in_range.sort_by(|(_, distance1), (_, distance2)| { + distance1.partial_cmp(distance2).unwrap_or(Ordering::Equal) + }); + + for (enemy_position, enemy_distance) in enemies_in_range { + let enemy_direction = (enemy_position.xy() - item_position.xy()).normalize(); + let obstacle_between_item_and_enemy = spatial_query.cast_ray( + *item_position, + enemy_direction, + enemy_distance, + false, + SpatialQueryFilter::new().with_masks([Layer::MapObstacle]), + ); + if obstacle_between_item_and_enemy.is_none() { + let mut item_entity_commands = commands.entity(item_entity); + item_entity_commands.with_children(|parent| { + parent.spawn(( + Name::new("Projectile"), + DamageEnemiesOnContact, + ProjectileBundle { + // Tags + tag: Projectile, + // Properties + damage, + // Physics + body: RigidBody::Dynamic, + position: item_position, + velocity: LinearVelocity(enemy_direction * projectile_speed), + collider: Collider::ball(PROJECTILE_RADIUS), + layers: CollisionLayers::new( + [Layer::Projectile, Layer::DamageEnemies], + [Layer::MapBound, Layer::MapObstacle, Layer::EnemyHitBox], + ), + // Texture + mesh: MaterialMesh2dBundle { + mesh: meshes + .add(shape::Circle::new(PROJECTILE_RADIUS).into()) + .into(), + material: materials.add(ColorMaterial::from(Color::DARK_GRAY)), + transform: Transform::from_translation(item_position.extend(3.00)), + ..default() + }, + }, + )); + }); + item_entity_commands.insert(Cooldown::::new(ATTACK_COOLDOWN)); + break; + } + } } } diff --git a/modes/survival/src/systems.rs b/modes/survival/src/systems.rs index b1e4ce1..aceea78 100644 --- a/modes/survival/src/systems.rs +++ b/modes/survival/src/systems.rs @@ -27,11 +27,15 @@ pub fn spawn_map(mut commands: Commands) { y_max: MAP_BOUND, }); commands.spawn((Name::new("Map"), Map, SpatialBundle::default())).with_children(|parent| { - // Define physics layer of the walls. - let layers = CollisionLayers::new([Layer::MapBound], [Layer::Player, Layer::Enemy]); + // Define physics layers of the walls. + let layers = CollisionLayers::new( + [Layer::MapBound], + [Layer::Player, Layer::Enemy, Layer::Projectile], + ); // Spawn left wall. parent.spawn(( Name::new("Left Wall"), + MapBound, RigidBody::Static, Collider::cuboid(50.0, MAP_BOUND * 2.0), layers, @@ -40,6 +44,7 @@ pub fn spawn_map(mut commands: Commands) { // Spawn top wall. parent.spawn(( Name::new("Top Wall"), + MapBound, RigidBody::Static, Collider::cuboid(MAP_BOUND * 2.0, 50.0), layers, @@ -48,6 +53,7 @@ pub fn spawn_map(mut commands: Commands) { // Spawn right wall. parent.spawn(( Name::new("Right Wall"), + MapBound, RigidBody::Static, Collider::cuboid(50.0, MAP_BOUND * 2.0), layers, @@ -56,6 +62,7 @@ pub fn spawn_map(mut commands: Commands) { // Spawn bottom wall. parent.spawn(( Name::new("Bottom Wall"), + MapBound, RigidBody::Static, Collider::cuboid(MAP_BOUND * 2.0, 50.0), layers, diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index e074295..12f20a1 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -64,6 +64,7 @@ pub fn spawn( collider: Collider::ball(PLAYER_SIZE), velocity: LinearVelocity(Vector::new(0.00, 0.00)), layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), + axes: LockedAxes::ROTATION_LOCKED, mesh: MaterialMesh2dBundle { mesh: meshes.add(shape::Circle::new(PLAYER_SIZE).into()).into(), material: materials.add(ColorMaterial::from(Color::GREEN)), @@ -82,7 +83,7 @@ pub fn spawn( PlayerHitBox, Sensor, Collider::ball(PLAYER_SIZE), - CollisionLayers::new([Layer::PlayerHitBox], [Layer::Enemy]), + CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), )); }); diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index 031800e..376f670 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -64,6 +64,7 @@ pub fn spawn( collider: Collider::ball(PLAYER_SIZE), velocity: LinearVelocity(Vector::new(0.0, 0.0)), layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), + axes: LockedAxes::ROTATION_LOCKED, mesh: MaterialMesh2dBundle { mesh: meshes.add(shape::Circle::new(PLAYER_SIZE).into()).into(), material: materials.add(ColorMaterial::from(Color::BLACK)), @@ -82,7 +83,7 @@ pub fn spawn( PlayerHitBox, Sensor, Collider::ball(PLAYER_SIZE), - CollisionLayers::new([Layer::PlayerHitBox], [Layer::Enemy]), + CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), )); }); From d2dfb90f0274d6b39f222aaba163d2033db7994d Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 20:59:38 +0300 Subject: [PATCH 02/38] feat: require collider method for players --- game/src/player/interfaces.rs | 3 +++ players/greek/src/artemis.rs | 15 ++++++++++----- players/greek/src/hades.rs | 15 ++++++++++----- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/game/src/player/interfaces.rs b/game/src/player/interfaces.rs index b97ee02..38d5859 100644 --- a/game/src/player/interfaces.rs +++ b/game/src/player/interfaces.rs @@ -17,6 +17,9 @@ pub trait Playable: Debug + Send + Sync + 'static { /// Gets the name of the player. fn name(&self) -> SmolStr; + /// Gets the collider of the player. + fn collider(&self) -> Collider; + /// Spawns the player. fn spawn(&self, world: &mut World); } diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index 12f20a1..f0230e3 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -8,7 +8,7 @@ use { }; /// Tag component for the player "Artemis". -#[derive(Component, Debug, Reflect)] +#[derive(Clone, Component, Debug, Reflect)] pub struct Artemis; impl Playable for Artemis { @@ -20,8 +20,12 @@ impl Playable for Artemis { "Artemis".into() } + fn collider(&self) -> Collider { + Collider::ball(PLAYER_SIZE) + } + fn spawn(&self, world: &mut World) { - world.run_system_once(spawn); + world.run_system_once_with(self.clone(), spawn); } } @@ -42,6 +46,7 @@ impl Plugin for ArtemisPlugin { /// Spawns the player "Artemis". pub fn spawn( + In(player): In, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, @@ -50,7 +55,7 @@ pub fn spawn( ) { commands .spawn(( - Artemis, + player.clone(), PlayerBundle { name: Name::new("Player"), tag: Player, @@ -61,7 +66,7 @@ pub fn spawn( body: RigidBody::Dynamic, restitution: Restitution::PERFECTLY_INELASTIC, position: Position(Vector::new(0.00, 0.00)), - collider: Collider::ball(PLAYER_SIZE), + collider: player.collider(), velocity: LinearVelocity(Vector::new(0.00, 0.00)), layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), axes: LockedAxes::ROTATION_LOCKED, @@ -82,7 +87,7 @@ pub fn spawn( Name::new("Hit Box"), PlayerHitBox, Sensor, - Collider::ball(PLAYER_SIZE), + player.collider(), CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), )); }); diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index 376f670..f69288c 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -8,7 +8,7 @@ use { }; /// Tag component for the player "Hades". -#[derive(Component, Debug, Reflect)] +#[derive(Clone, Component, Debug, Reflect)] pub struct Hades; impl Playable for Hades { @@ -20,8 +20,12 @@ impl Playable for Hades { "Hades".into() } + fn collider(&self) -> Collider { + Collider::ball(PLAYER_SIZE) + } + fn spawn(&self, world: &mut World) { - world.run_system_once(spawn); + world.run_system_once_with(self.clone(), spawn); } } @@ -42,6 +46,7 @@ impl Plugin for HadesPlugin { /// Spawns the player "Hades". pub fn spawn( + In(player): In, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, @@ -50,7 +55,7 @@ pub fn spawn( ) { commands .spawn(( - Hades, + player.clone(), PlayerBundle { name: Name::new("Player"), tag: Player, @@ -61,7 +66,7 @@ pub fn spawn( body: RigidBody::Dynamic, restitution: Restitution::PERFECTLY_INELASTIC, position: Position(Vector::new(0.00, 0.00)), - collider: Collider::ball(PLAYER_SIZE), + collider: player.collider(), velocity: LinearVelocity(Vector::new(0.0, 0.0)), layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), axes: LockedAxes::ROTATION_LOCKED, @@ -82,7 +87,7 @@ pub fn spawn( Name::new("Hit Box"), PlayerHitBox, Sensor, - Collider::ball(PLAYER_SIZE), + player.collider(), CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), )); }); From f2946a42a1de22554b898de23bbe9bc39bc73f20 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:02:37 +0300 Subject: [PATCH 03/38] refactor: remove damage component from player bundle --- game/src/player/components.rs | 1 - game/src/player/constants.rs | 3 --- players/greek/src/artemis.rs | 1 - players/greek/src/hades.rs | 1 - 4 files changed, 6 deletions(-) diff --git a/game/src/player/components.rs b/game/src/player/components.rs index bf01db5..2d4f06b 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -23,7 +23,6 @@ pub struct PlayerBundle { pub name: Name, pub tag: Player, // Properties - pub damage: Damage, pub health: Health, pub speed: Speed, // Combat diff --git a/game/src/player/constants.rs b/game/src/player/constants.rs index a3e5f2c..f522a7a 100644 --- a/game/src/player/constants.rs +++ b/game/src/player/constants.rs @@ -5,9 +5,6 @@ use crate::prelude::*; pub const PLAYER_SIZE: f32 = 20.00; -/// Initial damage of the player. -pub const INITIAL_PLAYER_DAMAGE: f32 = 5.00; - /// Initial health of the player. pub const INITIAL_PLAYER_HEALTH: f32 = 10.00; diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index f0230e3..268ea10 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -59,7 +59,6 @@ pub fn spawn( PlayerBundle { name: Name::new("Player"), tag: Player, - damage: Damage(INITIAL_PLAYER_DAMAGE), health: Health(INITIAL_PLAYER_HEALTH), speed: Speed(INITIAL_PLAYER_SPEED), remaining_health: RemainingHealth(INITIAL_PLAYER_HEALTH), diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index f69288c..d91d0c0 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -59,7 +59,6 @@ pub fn spawn( PlayerBundle { name: Name::new("Player"), tag: Player, - damage: Damage(INITIAL_PLAYER_DAMAGE), health: Health(INITIAL_PLAYER_HEALTH), speed: Speed(INITIAL_PLAYER_SPEED), remaining_health: RemainingHealth(INITIAL_PLAYER_HEALTH), From 8d8ec788898888a22c4e72f49f2fd118aa6c0850 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:04:18 +0300 Subject: [PATCH 04/38] style: improve naming of player constants --- game/src/player/constants.rs | 16 ++++++++-------- game/src/player/systems.rs | 4 ++-- players/greek/src/artemis.rs | 6 +++--- players/greek/src/hades.rs | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/game/src/player/constants.rs b/game/src/player/constants.rs index f522a7a..88b856c 100644 --- a/game/src/player/constants.rs +++ b/game/src/player/constants.rs @@ -5,15 +5,15 @@ use crate::prelude::*; pub const PLAYER_SIZE: f32 = 20.00; -/// Initial health of the player. -pub const INITIAL_PLAYER_HEALTH: f32 = 10.00; +/// Base health of players. +pub const BASE_PLAYER_HEALTH: f32 = 10.00; -/// Initial speed of the player. -pub const INITIAL_PLAYER_SPEED: f32 = 200.00; +/// Base speed of players. +pub const BASE_PLAYER_SPEED: f32 = 200.00; -/// Initial duration of dashing of the player. -pub const INITIAL_DASH_DURATION: Duration = Duration::from_millis(75); +/// Base duration of dashing of players. +pub const BASE_DASH_DURATION: Duration = Duration::from_millis(75); -/// Initial cooldown of dashing of the player. -pub const INITIAL_DASH_COOLDOWN: Duration = Duration::from_millis(1000); +/// Base cooldown of dashing of players. +pub const BASE_DASH_COOLDOWN: Duration = Duration::from_millis(1000); diff --git a/game/src/player/systems.rs b/game/src/player/systems.rs index c2c3626..d11edab 100644 --- a/game/src/player/systems.rs +++ b/game/src/player/systems.rs @@ -94,8 +94,8 @@ pub fn dash( return; } commands.entity(entity).insert(( - Dashing { timer: Timer::new(INITIAL_DASH_DURATION, TimerMode::Once) }, - Cooldown::::new(INITIAL_DASH_COOLDOWN), + Dashing { timer: Timer::new(BASE_DASH_DURATION, TimerMode::Once) }, + Cooldown::::new(BASE_DASH_COOLDOWN), )); } } diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index 268ea10..716cc37 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -59,9 +59,9 @@ pub fn spawn( PlayerBundle { name: Name::new("Player"), tag: Player, - health: Health(INITIAL_PLAYER_HEALTH), - speed: Speed(INITIAL_PLAYER_SPEED), - remaining_health: RemainingHealth(INITIAL_PLAYER_HEALTH), + health: Health(BASE_PLAYER_HEALTH), + speed: Speed(BASE_PLAYER_SPEED), + remaining_health: RemainingHealth(BASE_PLAYER_HEALTH), body: RigidBody::Dynamic, restitution: Restitution::PERFECTLY_INELASTIC, position: Position(Vector::new(0.00, 0.00)), diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index d91d0c0..bee933e 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -59,9 +59,9 @@ pub fn spawn( PlayerBundle { name: Name::new("Player"), tag: Player, - health: Health(INITIAL_PLAYER_HEALTH), - speed: Speed(INITIAL_PLAYER_SPEED), - remaining_health: RemainingHealth(INITIAL_PLAYER_HEALTH), + health: Health(BASE_PLAYER_HEALTH), + speed: Speed(BASE_PLAYER_SPEED), + remaining_health: RemainingHealth(BASE_PLAYER_HEALTH), body: RigidBody::Dynamic, restitution: Restitution::PERFECTLY_INELASTIC, position: Position(Vector::new(0.00, 0.00)), From b265c63a33a60c8ca5a3e7c09fe64c6376cde2c2 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:06:08 +0300 Subject: [PATCH 05/38] refactor: simplify hit box creation --- enemies/sweet/src/gummy_bear.rs | 18 ++++++------------ game/src/enemy/components.rs | 20 +++++++++++++++++--- game/src/player/components.rs | 20 +++++++++++++++++--- players/greek/src/artemis.rs | 8 +------- players/greek/src/hades.rs | 8 +------- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/enemies/sweet/src/gummy_bear.rs b/enemies/sweet/src/gummy_bear.rs index bbba91e..39fb8ea 100644 --- a/enemies/sweet/src/gummy_bear.rs +++ b/enemies/sweet/src/gummy_bear.rs @@ -7,7 +7,7 @@ use { }; /// Tag component for the enemy "Gummy Bear". -#[derive(Component, Debug, Reflect)] +#[derive(Clone, Component, Debug, Reflect)] pub struct GummyBear; impl Munchie for GummyBear { @@ -24,7 +24,7 @@ impl Munchie for GummyBear { } fn spawn(&self, world: &mut World, position: Position) { - world.run_system_once_with(position, spawn); + world.run_system_once_with((self.clone(), position), spawn); } } @@ -63,7 +63,7 @@ pub const DAMAGE_COOLDOWN: Duration = Duration::from_millis(1000); /// Spawns the enemy. pub fn spawn( - In(position): In, + In((enemy, position)): In<(GummyBear, Position)>, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, @@ -73,7 +73,7 @@ pub fn spawn( commands .spawn(( // Tag - GummyBear, + enemy.clone(), // Attack DamagePlayerOnContact, Damage(INITIAL_DAMAGE), @@ -92,7 +92,7 @@ pub fn spawn( body: RigidBody::Dynamic, restitution: Restitution::PERFECTLY_INELASTIC, position, - collider: GummyBear.collider(), + collider: enemy.collider(), layers: CollisionLayers::new( [Layer::Enemy, Layer::DamagePlayer], [Layer::MapBound, Layer::Enemy, Layer::PlayerHitBox], @@ -107,12 +107,6 @@ pub fn spawn( }, )) .with_children(|parent| { - parent.spawn(( - Name::new("Hit Box"), - EnemyHitBox, - Sensor, - Collider::ball(SIZE), - CollisionLayers::new([Layer::EnemyHitBox], [Layer::DamageEnemies]), - )); + parent.spawn(EnemyHitBox::bundle(&enemy)); }); } diff --git a/game/src/enemy/components.rs b/game/src/enemy/components.rs index cb8e82b..35122c7 100644 --- a/game/src/enemy/components.rs +++ b/game/src/enemy/components.rs @@ -2,17 +2,31 @@ use crate::prelude::*; /// Tag component for enemies. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct Enemy; /// Tag component for hit boxes of enemies. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct EnemyHitBox; +impl EnemyHitBox { + pub fn bundle(enemy: &E) -> impl Bundle { + ( + // Tags + Name::new("Hit Box"), + EnemyHitBox, + // Physics + enemy.collider(), + CollisionLayers::new([Layer::EnemyHitBox], [Layer::DamageEnemies]), + Sensor, + ) + } +} + /// Tag component for entities that apply damage to enemies on contact. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct DamageEnemiesOnContact; diff --git a/game/src/player/components.rs b/game/src/player/components.rs index 2d4f06b..af9bc18 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -2,17 +2,31 @@ use crate::prelude::*; /// Tag component for the player. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct Player; /// Tag component for the hit box of the player. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct PlayerHitBox; +impl PlayerHitBox { + pub fn bundle(player: &P) -> impl Bundle { + ( + // Tags + Name::new("Hit Box"), + PlayerHitBox, + // Physics + player.collider(), + CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), + Sensor, + ) + } +} + /// Tag component for entities that apply damage to the player on contact. -#[derive(Component, Debug, Reflect)] +#[derive(Component, Debug, Default, Reflect)] pub struct DamagePlayerOnContact; diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index 716cc37..9d4d28c 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -82,13 +82,7 @@ pub fn spawn( }, )) .with_children(|parent| { - parent.spawn(( - Name::new("Hit Box"), - PlayerHitBox, - Sensor, - player.collider(), - CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), - )); + parent.spawn(PlayerHitBox::bundle(&player)); }); inventory.add(BowOfArtemis.instantiate()); diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index bee933e..074c460 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -82,13 +82,7 @@ pub fn spawn( }, )) .with_children(|parent| { - parent.spawn(( - Name::new("Hit Box"), - PlayerHitBox, - Sensor, - player.collider(), - CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), - )); + parent.spawn(PlayerHitBox::bundle(&player)); }); inventory.add(BidentOfHades.instantiate()); From a780faba1f1aa0c62834c1f213149ff7f0ebf109 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:13:20 +0300 Subject: [PATCH 06/38] refactor: move player size constant to player implementations --- game/src/player/constants.rs | 4 ---- players/greek/src/artemis.rs | 3 +++ players/greek/src/hades.rs | 3 +++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/game/src/player/constants.rs b/game/src/player/constants.rs index 88b856c..868c4d7 100644 --- a/game/src/player/constants.rs +++ b/game/src/player/constants.rs @@ -1,10 +1,6 @@ use crate::prelude::*; -/// Size of the player. -pub const PLAYER_SIZE: f32 = 20.00; - - /// Base health of players. pub const BASE_PLAYER_HEALTH: f32 = 10.00; diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index 9d4d28c..ab177df 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -44,6 +44,9 @@ impl Plugin for ArtemisPlugin { } } +/// Size of the player. +pub const PLAYER_SIZE: f32 = 20.00; + /// Spawns the player "Artemis". pub fn spawn( In(player): In, diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index 074c460..135a832 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -44,6 +44,9 @@ impl Plugin for HadesPlugin { } } +/// Size of the player. +pub const PLAYER_SIZE: f32 = 20.00; + /// Spawns the player "Hades". pub fn spawn( In(player): In, From 559806be2ed539c2987f5b3317565211030f69f7 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:39:59 +0300 Subject: [PATCH 07/38] feat: add typed builder as a dependency --- game/Cargo.toml | 1 + game/src/prelude.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/game/Cargo.toml b/game/Cargo.toml index f90e68d..6715cdd 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -19,6 +19,7 @@ smallvec = { version = "1.11", features = ["serde"] } smol_str = { version = "0.2" } strum = { version = "0.25" } strum_macros = { version = "0.25" } +typed-builder = { version = "0.18" } [target.'cfg(not(target_family = "wasm"))'.dependencies] bevy-persistent-windows = { version = "0.4" } diff --git a/game/src/prelude.rs b/game/src/prelude.rs index 43e9a03..9e38ef6 100644 --- a/game/src/prelude.rs +++ b/game/src/prelude.rs @@ -129,6 +129,7 @@ pub use { }, strum::IntoEnumIterator, strum_macros::EnumIter, + typed_builder::TypedBuilder, }; #[cfg(feature = "native")] From 597da561fea1a3310973047a4aa9375bec65a1c6 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 21:40:39 +0300 Subject: [PATCH 08/38] refactor: improve the api of spawning players --- game/src/player/components.rs | 71 ++++++++++++++++++++++++----------- game/src/player/constants.rs | 4 +- game/src/player/interfaces.rs | 15 +++++++- game/src/prelude.rs | 5 ++- players/greek/src/artemis.rs | 60 +++++++++++------------------ players/greek/src/hades.rs | 60 +++++++++++------------------ 6 files changed, 110 insertions(+), 105 deletions(-) diff --git a/game/src/player/components.rs b/game/src/player/components.rs index af9bc18..9df5ea2 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -11,13 +11,13 @@ pub struct Player; pub struct PlayerHitBox; impl PlayerHitBox { - pub fn bundle(player: &P) -> impl Bundle { + pub fn bundle(collider: Collider) -> impl Bundle { ( // Tags Name::new("Hit Box"), PlayerHitBox, // Physics - player.collider(), + collider, CollisionLayers::new([Layer::PlayerHitBox], [Layer::DamagePlayer]), Sensor, ) @@ -31,26 +31,53 @@ pub struct DamagePlayerOnContact; /// Bundle for players. -#[derive(Bundle)] -pub struct PlayerBundle { - // Tags - pub name: Name, - pub tag: Player, - // Properties - pub health: Health, - pub speed: Speed, - // Combat - pub remaining_health: RemainingHealth, - // Physics - pub body: RigidBody, - pub restitution: Restitution, - pub position: Position, - pub collider: Collider, - pub velocity: LinearVelocity, - pub layers: CollisionLayers, - pub axes: LockedAxes, - // Texture +#[derive(Bundle, TypedBuilder)] +pub struct PlayerBundle { + pub player: P, pub mesh: MaterialMesh2dBundle, - // Input + #[builder(setter(transform = + |input_map: InputMap| { + InputManagerBundle:: { action_state: ActionState::default(), input_map } + } + ))] pub input: InputManagerBundle, } + +impl PlayerBundle

{ + /// Spawns the player. + pub fn spawn<'w, 's, 'a>( + self, + commands: &'a mut Commands<'w, 's>, + ) -> EntityCommands<'w, 's, 'a> { + let name = format!("Player [{}]", self.player.name()); + let health = self.player.health(); + let speed = self.player.speed(); + let collider = self.player.collider(); + + let mut player = commands.spawn(( + // Tags + Name::new(name), + Player, + // Player + self, + health, + speed, + // Combat + RemainingHealth(*health), + // Physics + RigidBody::Dynamic, + Position::from_xy(0.00, 0.00), + LinearVelocity::ZERO, + Restitution::PERFECTLY_INELASTIC, + LockedAxes::ROTATION_LOCKED, + collider.clone(), + CollisionLayers::new([Layer::Player], [Layer::MapBound]), + )); + + player.with_children(|parent| { + parent.spawn(PlayerHitBox::bundle(collider)); + }); + + player + } +} diff --git a/game/src/player/constants.rs b/game/src/player/constants.rs index 868c4d7..5cee579 100644 --- a/game/src/player/constants.rs +++ b/game/src/player/constants.rs @@ -2,10 +2,10 @@ use crate::prelude::*; /// Base health of players. -pub const BASE_PLAYER_HEALTH: f32 = 10.00; +pub const BASE_HEALTH: f32 = 10.00; /// Base speed of players. -pub const BASE_PLAYER_SPEED: f32 = 200.00; +pub const BASE_SPEED: f32 = 200.00; /// Base duration of dashing of players. diff --git a/game/src/player/interfaces.rs b/game/src/player/interfaces.rs index 38d5859..0dd8c90 100644 --- a/game/src/player/interfaces.rs +++ b/game/src/player/interfaces.rs @@ -1,4 +1,7 @@ -use crate::prelude::*; +use crate::{ + player::constants::*, + prelude::*, +}; /// Interface for mythologies. @@ -17,9 +20,17 @@ pub trait Playable: Debug + Send + Sync + 'static { /// Gets the name of the player. fn name(&self) -> SmolStr; + /// Gets the health of the player. + fn health(&self) -> Health { + Health(BASE_HEALTH) + } + /// Gets the speed of the player. + fn speed(&self) -> Speed { + Speed(BASE_SPEED) + } + /// Gets the collider of the player. fn collider(&self) -> Collider; - /// Spawns the player. fn spawn(&self, world: &mut World); } diff --git a/game/src/prelude.rs b/game/src/prelude.rs index 9e38ef6..c93c638 100644 --- a/game/src/prelude.rs +++ b/game/src/prelude.rs @@ -67,7 +67,10 @@ pub use { }, ecs::{ self as bevy_ecs, - system::RunSystemOnce, + system::{ + EntityCommands, + RunSystemOnce, + }, }, input::mouse::MouseMotion, log::{ diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index ab177df..2071141 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -1,12 +1,15 @@ use { crate::prelude::*, greek_items::prelude::*, - mythmallow::{ - player::constants::*, - prelude::*, - }, + mythmallow::prelude::*, }; +/// Size of the player. +pub const SIZE: f32 = 20.00; + +/// Color of the player. +pub const COLOR: Color = Color::GREEN; + /// Tag component for the player "Artemis". #[derive(Clone, Component, Debug, Reflect)] pub struct Artemis; @@ -21,7 +24,7 @@ impl Playable for Artemis { } fn collider(&self) -> Collider { - Collider::ball(PLAYER_SIZE) + Collider::ball(SIZE) } fn spawn(&self, world: &mut World) { @@ -44,9 +47,6 @@ impl Plugin for ArtemisPlugin { } } -/// Size of the player. -pub const PLAYER_SIZE: f32 = 20.00; - /// Spawns the player "Artemis". pub fn spawn( In(player): In, @@ -56,37 +56,19 @@ pub fn spawn( game_action_input_map: Res>, mut inventory: ResMut, ) { - commands - .spawn(( - player.clone(), - PlayerBundle { - name: Name::new("Player"), - tag: Player, - health: Health(BASE_PLAYER_HEALTH), - speed: Speed(BASE_PLAYER_SPEED), - remaining_health: RemainingHealth(BASE_PLAYER_HEALTH), - body: RigidBody::Dynamic, - restitution: Restitution::PERFECTLY_INELASTIC, - position: Position(Vector::new(0.00, 0.00)), - collider: player.collider(), - velocity: LinearVelocity(Vector::new(0.00, 0.00)), - layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), - axes: LockedAxes::ROTATION_LOCKED, - mesh: MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(PLAYER_SIZE).into()).into(), - material: materials.add(ColorMaterial::from(Color::GREEN)), - transform: Transform::from_translation(Vec3::new(0.00, 0.00, 2.00)), - ..default() - }, - input: InputManagerBundle:: { - action_state: ActionState::default(), - input_map: game_action_input_map.clone(), - }, - }, - )) - .with_children(|parent| { - parent.spawn(PlayerHitBox::bundle(&player)); - }); + let mesh = MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), + material: materials.add(ColorMaterial::from(COLOR)), + transform: Transform::from_translation(Vec3::new(0.00, 0.00, 2.00)), + ..default() + }; + + PlayerBundle::builder() + .player(player) + .mesh(mesh) + .input(game_action_input_map.clone()) + .build() + .spawn(&mut commands); inventory.add(BowOfArtemis.instantiate()); } diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index 135a832..c09ea8e 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -1,12 +1,15 @@ use { crate::prelude::*, greek_items::prelude::*, - mythmallow::{ - player::constants::*, - prelude::*, - }, + mythmallow::prelude::*, }; +/// Size of the player. +pub const SIZE: f32 = 20.00; + +/// Color of the player. +pub const COLOR: Color = Color::BLACK; + /// Tag component for the player "Hades". #[derive(Clone, Component, Debug, Reflect)] pub struct Hades; @@ -21,7 +24,7 @@ impl Playable for Hades { } fn collider(&self) -> Collider { - Collider::ball(PLAYER_SIZE) + Collider::ball(SIZE) } fn spawn(&self, world: &mut World) { @@ -44,9 +47,6 @@ impl Plugin for HadesPlugin { } } -/// Size of the player. -pub const PLAYER_SIZE: f32 = 20.00; - /// Spawns the player "Hades". pub fn spawn( In(player): In, @@ -56,37 +56,19 @@ pub fn spawn( game_action_input_map: Res>, mut inventory: ResMut, ) { - commands - .spawn(( - player.clone(), - PlayerBundle { - name: Name::new("Player"), - tag: Player, - health: Health(BASE_PLAYER_HEALTH), - speed: Speed(BASE_PLAYER_SPEED), - remaining_health: RemainingHealth(BASE_PLAYER_HEALTH), - body: RigidBody::Dynamic, - restitution: Restitution::PERFECTLY_INELASTIC, - position: Position(Vector::new(0.00, 0.00)), - collider: player.collider(), - velocity: LinearVelocity(Vector::new(0.0, 0.0)), - layers: CollisionLayers::new([Layer::Player], [Layer::MapBound]), - axes: LockedAxes::ROTATION_LOCKED, - mesh: MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(PLAYER_SIZE).into()).into(), - material: materials.add(ColorMaterial::from(Color::BLACK)), - transform: Transform::from_translation(Vec3::new(0.00, 0.00, 2.00)), - ..default() - }, - input: InputManagerBundle:: { - action_state: ActionState::default(), - input_map: game_action_input_map.clone(), - }, - }, - )) - .with_children(|parent| { - parent.spawn(PlayerHitBox::bundle(&player)); - }); + let mesh = MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), + material: materials.add(ColorMaterial::from(COLOR)), + transform: Transform::from_translation(Vec3::new(0.00, 0.00, 2.00)), + ..default() + }; + + PlayerBundle::builder() + .player(player) + .mesh(mesh) + .input(game_action_input_map.clone()) + .build() + .spawn(&mut commands); inventory.add(BidentOfHades.instantiate()); } From 5edd438f0d42d7fd06b59a3a635b17382ebe90c8 Mon Sep 17 00:00:00 2001 From: Umut Date: Mon, 1 Jan 2024 22:30:05 +0300 Subject: [PATCH 09/38] refactor: improve the api of spawning enemies --- enemies/sweet/src/gummy_bear.rs | 97 ++++++++++++++------------------- game/src/enemy/components.rs | 77 ++++++++++++++++++++------ game/src/enemy/interfaces.rs | 10 +++- 3 files changed, 109 insertions(+), 75 deletions(-) diff --git a/enemies/sweet/src/gummy_bear.rs b/enemies/sweet/src/gummy_bear.rs index 39fb8ea..692d88e 100644 --- a/enemies/sweet/src/gummy_bear.rs +++ b/enemies/sweet/src/gummy_bear.rs @@ -6,7 +6,22 @@ use { }, }; -/// Tag component for the enemy "Gummy Bear". +/// Size of the enemy. +pub const SIZE: f32 = 15.00; + +/// Health of the enemy. +pub const HEALTH: f32 = 5.00; + +/// Speed of the enemy. +pub const SPEED: f32 = 100.00; + +/// Contact damage of the enemy. +pub const CONTACT_DAMAGE: f32 = 3.00; + +/// Cooldown of contact damage of the enemy. +pub const CONTACT_DAMAGE_COOLDOWN: Duration = Duration::from_millis(1000); + +/// Component for the enemy "Gummy Bear". #[derive(Clone, Component, Debug, Reflect)] pub struct GummyBear; @@ -19,6 +34,18 @@ impl Munchie for GummyBear { "Gummy Bear".into() } + fn contact_damage(&self) -> Option<(Damage, DamageCooldown)> { + Some((Damage(CONTACT_DAMAGE), DamageCooldown::new(CONTACT_DAMAGE_COOLDOWN))) + } + + fn health(&self) -> Health { + Health(HEALTH) + } + + fn speed(&self) -> Speed { + Speed(SPEED) + } + fn collider(&self) -> Collider { Collider::ball(SIZE) } @@ -46,21 +73,6 @@ impl Plugin for GummyBearPlugin { } } -/// Size of the enemy. -pub const SIZE: f32 = 15.00; - -/// Initial damage of the enemy. -pub const INITIAL_DAMAGE: f32 = 3.00; - -/// Initial health of the enemy. -pub const INITIAL_HEALTH: f32 = 5.00; - -/// Initial speed of the enemy. -pub const INITIAL_SPEED: f32 = 100.00; - -/// Cooldown of applying damage to the player when in contact. -pub const DAMAGE_COOLDOWN: Duration = Duration::from_millis(1000); - /// Spawns the enemy. pub fn spawn( In((enemy, position)): In<(GummyBear, Position)>, @@ -69,44 +81,17 @@ pub fn spawn( mut materials: ResMut>, mut counter: ResMut, ) { - counter.increment(); - commands - .spawn(( - // Tag - enemy.clone(), - // Attack - DamagePlayerOnContact, - Damage(INITIAL_DAMAGE), - DamageCooldown::new(DAMAGE_COOLDOWN), - // Enemy - EnemyBundle { - // Tags - name: Name::new(format!("Enemy {} [Gummy Bear]", counter.get())), - tag: Enemy, - // Properties - health: Health(INITIAL_HEALTH), - speed: Speed(INITIAL_SPEED), - // Combat - remaining_health: RemainingHealth(INITIAL_HEALTH), - // Physics - body: RigidBody::Dynamic, - restitution: Restitution::PERFECTLY_INELASTIC, - position, - collider: enemy.collider(), - layers: CollisionLayers::new( - [Layer::Enemy, Layer::DamagePlayer], - [Layer::MapBound, Layer::Enemy, Layer::PlayerHitBox], - ), - // Texture - mesh: MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), - material: materials.add(ColorMaterial::from(Color::RED)), - transform: Transform::from_translation(Vec3::new(position.x, position.y, 1.00)), - ..default() - }, - }, - )) - .with_children(|parent| { - parent.spawn(EnemyHitBox::bundle(&enemy)); - }); + let mesh = MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(SIZE).into()).into(), + material: materials.add(ColorMaterial::from(Color::RED)), + transform: Transform::from_translation(Vec3::new(position.x, position.y, 1.00)), + ..default() + }; + + EnemyBundle::builder() + .enemy(enemy) + .position(position) + .mesh(mesh) + .build() + .spawn(&mut commands, &mut counter); } diff --git a/game/src/enemy/components.rs b/game/src/enemy/components.rs index 35122c7..eeaa5f4 100644 --- a/game/src/enemy/components.rs +++ b/game/src/enemy/components.rs @@ -11,13 +11,13 @@ pub struct Enemy; pub struct EnemyHitBox; impl EnemyHitBox { - pub fn bundle(enemy: &E) -> impl Bundle { + pub fn bundle(collider: Collider) -> impl Bundle { ( // Tags Name::new("Hit Box"), EnemyHitBox, // Physics - enemy.collider(), + collider, CollisionLayers::new([Layer::EnemyHitBox], [Layer::DamageEnemies]), Sensor, ) @@ -31,22 +31,63 @@ pub struct DamageEnemiesOnContact; /// Bundle for enemies. -#[derive(Bundle)] -pub struct EnemyBundle { - // Tags - pub name: Name, - pub tag: Enemy, - // Properties - pub health: Health, - pub speed: Speed, - // Combat - pub remaining_health: RemainingHealth, - // Physics - pub body: RigidBody, - pub restitution: Restitution, +#[derive(Bundle, TypedBuilder)] +pub struct EnemyBundle { + pub enemy: E, pub position: Position, - pub collider: Collider, - pub layers: CollisionLayers, - // Texture pub mesh: MaterialMesh2dBundle, } + +impl EnemyBundle { + /// Spawns the enemy. + pub fn spawn<'w, 's, 'a>( + self, + commands: &'a mut Commands<'w, 's>, + counter: &mut EnemyCounter, + ) -> EntityCommands<'w, 's, 'a> { + counter.increment(); + + let name = self.enemy.name(); + let contact_damage = self.enemy.contact_damage(); + let health = self.enemy.health(); + let speed = self.enemy.speed(); + let collider = self.enemy.collider(); + + let mut collision_layers = + CollisionLayers::new([Layer::Enemy], [Layer::MapBound, Layer::Enemy]); + + if contact_damage.is_some() { + collision_layers = collision_layers.add_group(Layer::DamagePlayer); + collision_layers = collision_layers.add_mask(Layer::PlayerHitBox); + } + + let mut enemy = commands.spawn(( + // Tags + Name::new(format!("Enemy {} [{}]", counter.get(), name)), + Enemy, + // Properties + self, + health, + speed, + // Combat + RemainingHealth(*health), + // Physics + RigidBody::Dynamic, + LinearVelocity::ZERO, + Restitution::PERFECTLY_INELASTIC, + LockedAxes::ROTATION_LOCKED, + collider.clone(), + collision_layers, + )); + + enemy.with_children(|parent| { + parent.spawn(EnemyHitBox::bundle(collider)); + }); + + if let Some((damage, cooldown)) = contact_damage { + enemy.insert((DamagePlayerOnContact, damage, cooldown)); + } + + enemy + } +} diff --git a/game/src/enemy/interfaces.rs b/game/src/enemy/interfaces.rs index b8af509..f66ddcf 100644 --- a/game/src/enemy/interfaces.rs +++ b/game/src/enemy/interfaces.rs @@ -21,9 +21,17 @@ pub trait Munchie: Debug + Send + Sync + 'static { /// Gets the name of the enemy. fn name(&self) -> SmolStr; + /// Gets the contact damage of the enemy. + fn contact_damage(&self) -> Option<(Damage, DamageCooldown)> { + None + } + /// Gets the health of the enemy. + fn health(&self) -> Health; + /// Gets the speed of the enemy. + fn speed(&self) -> Speed; + /// Gets the collider of the enemy. fn collider(&self) -> Collider; - /// Spawns the enemy. fn spawn(&self, world: &mut World, position: Position); } From d8bd03de54647791a763f1f94a75d87adc0a640b Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:23:59 +0300 Subject: [PATCH 10/38] style: fix ordering of content modules in the top level library --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 468fd79..3696af9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,16 @@ #![doc = include_str!("../README.md")] pub use mythmallow_game::*; pub mod content { - pub mod modes { - pub mod survival { - pub use mythmallow_mode_survival::*; - } - } pub mod items { pub mod greek { pub use mythmallow_items_greek::*; } } + pub mod modes { + pub mod survival { + pub use mythmallow_mode_survival::*; + } + } pub mod players { pub mod greek { pub use mythmallow_players_greek::*; From 2c746a33a09f9bd10426e1fe2b8657b8a5d62049 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:24:31 +0300 Subject: [PATCH 11/38] feat: create enemies content module in the top level library --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3696af9..5973694 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ #![doc = include_str!("../README.md")] pub use mythmallow_game::*; pub mod content { + pub mod enemies { + pub mod sweet { + pub use mythmallow_enemies_sweet::*; + } + } pub mod items { pub mod greek { pub use mythmallow_items_greek::*; From 036956fc8e0f74468388f54b093559a81298367b Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:30:42 +0300 Subject: [PATCH 12/38] ci: only cancel ci runs in pull requests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 420e84a..622c0b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + cancel-in-progress: ${{ github.event_name != 'pull_request' }} jobs: conformance: From df24ddae44926d3f33059d1d046a4d12d0f72479 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:33:19 +0300 Subject: [PATCH 13/38] fix: use correct project name on the title of the main window --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 262ddae..a1db55b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -125,7 +125,7 @@ fn initialize(app: &mut App, args: &Args) { Name::new("Primary Window"), PrimaryWindow, PersistentWindowBundle { - window: Window { title: "Mythmellow".to_owned(), ..Default::default() }, + window: Window { title: "Mythmallow".to_owned(), ..Default::default() }, state: Persistent::::builder() .name("window state") .format(StorageFormat::Toml) From 71bd9269a05882c85be7f7122581ad6d341737fb Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:38:11 +0300 Subject: [PATCH 14/38] style: improve style of item interface --- game/src/items/interfaces.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/game/src/items/interfaces.rs b/game/src/items/interfaces.rs index f327234..0fa3da9 100644 --- a/game/src/items/interfaces.rs +++ b/game/src/items/interfaces.rs @@ -10,7 +10,6 @@ pub trait Item: Debug + Send + Sync + 'static { /// Instantiates the item to add it to the inventory. fn instantiate(&self) -> ItemInstance; - // Acquires the item. fn acquire(&self, world: &mut World) -> Option; // Releases the item. From a0ae14da1d0fd30a8d377c315599aa29d6c1669d Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:39:08 +0300 Subject: [PATCH 15/38] refactor: remove explicit position component from the player and let physics engine take care of it --- game/src/player/components.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/game/src/player/components.rs b/game/src/player/components.rs index 9df5ea2..795f257 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -66,7 +66,6 @@ impl PlayerBundle

{ RemainingHealth(*health), // Physics RigidBody::Dynamic, - Position::from_xy(0.00, 0.00), LinearVelocity::ZERO, Restitution::PERFECTLY_INELASTIC, LockedAxes::ROTATION_LOCKED, From e413cc3faffc6898e282c6167b11704e95332010 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 15:40:27 +0300 Subject: [PATCH 16/38] feat: make spawned items the child of the player automatically --- game/src/inventory/systems.rs | 8 ++++++ items/greek/src/bow_of_artemis.rs | 45 ++++++++++++------------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/game/src/inventory/systems.rs b/game/src/inventory/systems.rs index b293246..d60c3ea 100644 --- a/game/src/inventory/systems.rs +++ b/game/src/inventory/systems.rs @@ -25,6 +25,14 @@ pub fn acquire_release_items(world: &mut World) { new_items.push(Arc::new(item_to_acquire)); } + if let Ok(player_entity) = world.query_filtered::>().get_single(world) { + for new_item in &new_items { + if let Some(new_item_entity) = new_item.entity { + world.entity_mut(player_entity).add_child(new_item_entity); + } + } + } + let mut inventory = world.resource_mut::(); inventory.items.extend(new_items); } diff --git a/items/greek/src/bow_of_artemis.rs b/items/greek/src/bow_of_artemis.rs index be56c79..f5cd933 100644 --- a/items/greek/src/bow_of_artemis.rs +++ b/items/greek/src/bow_of_artemis.rs @@ -3,6 +3,21 @@ use { mythmallow::prelude::*, }; +/// Base range of the item. +pub const BASE_RANGE: f32 = 250.00; + +/// Base damage of the item. +pub const BASE_DAMAGE: Damage = Damage(5.00); + +/// Cooldown duration for the attack with the item. +pub const ATTACK_COOLDOWN: Duration = Duration::from_millis(600); + +/// Size of the projectiles of the item. +pub const PROJECTILE_SIZE: f32 = 3.00; + +/// Base speed for the projectiles of the item. +pub const BASE_PROJECTILE_SPEED: f32 = 250.00; + /// Tag component for the item "Bow of Artemis". #[derive(Clone, Component, Debug, Reflect)] pub struct BowOfArtemis; @@ -49,21 +64,6 @@ impl Plugin for BowOfArtemisPlugin { } } -/// Cooldown duration for the attack with the item. -pub const ATTACK_COOLDOWN: Duration = Duration::from_millis(600); - -/// Base range of the item. -pub const BASE_RANGE: f32 = 250.00; - -/// Base damage of the item. -pub const BASE_DAMAGE: Damage = Damage(5.00); - -/// Radius of the projectiles of the item. -pub const PROJECTILE_RADIUS: f32 = 3.00; - -/// Base speed for the projectiles of the item. -pub const BASE_PROJECTILE_SPEED: f32 = 250.00; - /// Acquires the item. pub fn acquire( In(item): In, @@ -71,13 +71,7 @@ pub fn acquire( mut meshes: ResMut>, mut materials: ResMut>, inventory: Res, - player_query: Query>, ) -> Option { - let player_entity = match player_query.get_single() { - Ok(query_result) => query_result, - Err(_) => return None, - }; - let item = commands .spawn(( Name::new(format!("Item {} ({})", inventory.items.len(), item.name().to_string())), @@ -90,9 +84,6 @@ pub fn acquire( }, )) .id(); - - commands.entity(player_entity).add_child(item); - Some(item) } @@ -165,16 +156,14 @@ pub fn attack( body: RigidBody::Dynamic, position: item_position, velocity: LinearVelocity(enemy_direction * projectile_speed), - collider: Collider::ball(PROJECTILE_RADIUS), + collider: Collider::ball(PROJECTILE_SIZE), layers: CollisionLayers::new( [Layer::Projectile, Layer::DamageEnemies], [Layer::MapBound, Layer::MapObstacle, Layer::EnemyHitBox], ), // Texture mesh: MaterialMesh2dBundle { - mesh: meshes - .add(shape::Circle::new(PROJECTILE_RADIUS).into()) - .into(), + mesh: meshes.add(shape::Circle::new(PROJECTILE_SIZE).into()).into(), material: materials.add(ColorMaterial::from(Color::DARK_GRAY)), transform: Transform::from_translation(item_position.extend(3.00)), ..default() From 9eaaa886011ce343301a1884ff394aca88417426 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 16:00:29 +0300 Subject: [PATCH 17/38] style: improve naming of interfaces --- enemies/sweet/src/gummy_bear.rs | 4 ++-- enemies/sweet/src/pack.rs | 4 ++-- enemies/sweet/src/prelude.rs | 2 +- game/src/enemy/components.rs | 4 ++-- game/src/enemy/interfaces.rs | 4 ++-- game/src/enemy/registry.rs | 18 +++++++++--------- game/src/enemy/resources.rs | 6 +++--- game/src/inventory/resources.rs | 8 ++++---- game/src/items/interfaces.rs | 2 +- game/src/items/registry.rs | 10 +++++----- game/src/mode/conditions.rs | 2 +- game/src/mode/interfaces.rs | 2 +- game/src/mode/registry.rs | 8 ++++---- game/src/mode/resources.rs | 4 ++-- game/src/player/components.rs | 4 ++-- game/src/player/interfaces.rs | 4 ++-- game/src/player/registry.rs | 12 ++++++------ game/src/player/resources.rs | 2 +- items/greek/src/bident_of_hades.rs | 2 +- items/greek/src/bow_of_artemis.rs | 2 +- modes/survival/src/mode.rs | 2 +- players/greek/src/artemis.rs | 2 +- players/greek/src/hades.rs | 2 +- players/greek/src/mythology.rs | 2 +- 24 files changed, 56 insertions(+), 56 deletions(-) diff --git a/enemies/sweet/src/gummy_bear.rs b/enemies/sweet/src/gummy_bear.rs index 692d88e..c65b220 100644 --- a/enemies/sweet/src/gummy_bear.rs +++ b/enemies/sweet/src/gummy_bear.rs @@ -25,7 +25,7 @@ pub const CONTACT_DAMAGE_COOLDOWN: Duration = Duration::from_millis(1000); #[derive(Clone, Component, Debug, Reflect)] pub struct GummyBear; -impl Munchie for GummyBear { +impl IEnemy for GummyBear { fn id(&self) -> SmolStr { "gummy-bear".into() } @@ -62,7 +62,7 @@ impl Plugin for GummyBearPlugin { fn build(&self, app: &mut App) { // Register the enemy. let mut enemy_registry = ENEMY_REGISTRY.lock().unwrap(); - enemy_registry.register(SweetMunchiesPack, GummyBear).add_tag(MELEE_ENEMY_TAG); + enemy_registry.register(SweetEnemyPack, GummyBear).add_tag(MELEE_ENEMY_TAG); drop(enemy_registry); // Register components. diff --git a/enemies/sweet/src/pack.rs b/enemies/sweet/src/pack.rs index e847a68..92c1772 100644 --- a/enemies/sweet/src/pack.rs +++ b/enemies/sweet/src/pack.rs @@ -2,9 +2,9 @@ use mythmallow::prelude::*; /// Sweet enemies. #[derive(Debug)] -pub struct SweetMunchiesPack; +pub struct SweetEnemyPack; -impl MunchiePack for SweetMunchiesPack { +impl IEnemyPack for SweetEnemyPack { fn id(&self) -> SmolStr { "sweet".into() } diff --git a/enemies/sweet/src/prelude.rs b/enemies/sweet/src/prelude.rs index 30e7a73..f1ffdd9 100644 --- a/enemies/sweet/src/prelude.rs +++ b/enemies/sweet/src/prelude.rs @@ -1,5 +1,5 @@ pub use crate::{ gummy_bear::GummyBear, - pack::SweetMunchiesPack, + pack::SweetEnemyPack, plugin::SweetEnemiesPlugin, }; diff --git a/game/src/enemy/components.rs b/game/src/enemy/components.rs index eeaa5f4..fdbee52 100644 --- a/game/src/enemy/components.rs +++ b/game/src/enemy/components.rs @@ -32,13 +32,13 @@ pub struct DamageEnemiesOnContact; /// Bundle for enemies. #[derive(Bundle, TypedBuilder)] -pub struct EnemyBundle { +pub struct EnemyBundle { pub enemy: E, pub position: Position, pub mesh: MaterialMesh2dBundle, } -impl EnemyBundle { +impl EnemyBundle { /// Spawns the enemy. pub fn spawn<'w, 's, 'a>( self, diff --git a/game/src/enemy/interfaces.rs b/game/src/enemy/interfaces.rs index f66ddcf..f5d900f 100644 --- a/game/src/enemy/interfaces.rs +++ b/game/src/enemy/interfaces.rs @@ -1,7 +1,7 @@ use crate::prelude::*; /// Interface for enemy packs. -pub trait MunchiePack: Any + Debug + Send + Sync + 'static { +pub trait IEnemyPack: Any + Debug + Send + Sync + 'static { /// Gets the unique identifier of the enemy pack. fn id(&self) -> SmolStr; /// Gets the name of the enemy pack. @@ -15,7 +15,7 @@ pub trait MunchiePack: Any + Debug + Send + Sync + 'static { } /// Interface for enemies. -pub trait Munchie: Debug + Send + Sync + 'static { +pub trait IEnemy: Debug + Send + Sync + 'static { /// Gets the unique identifier of the enemy. fn id(&self) -> SmolStr; /// Gets the name of the enemy. diff --git a/game/src/enemy/registry.rs b/game/src/enemy/registry.rs index d0829fc..3559de4 100644 --- a/game/src/enemy/registry.rs +++ b/game/src/enemy/registry.rs @@ -5,7 +5,7 @@ pub static ENEMY_REGISTRY: Mutex = Mutex::new(EnemyRegistry::new( /// Container for enemy registry. #[derive(Default, Deref, DerefMut, Resource)] -pub struct EnemyRegistry(pub Vec<(Arc, Vec)>); +pub struct EnemyRegistry(pub Vec<(Arc, Vec)>); impl EnemyRegistry { /// Creates a new enemy registry. @@ -18,8 +18,8 @@ impl EnemyRegistry { /// Registers an enemy to enemy registry. pub fn register( &mut self, - pack: impl MunchiePack, - enemy: impl Munchie, + pack: impl IEnemyPack, + enemy: impl IEnemy, ) -> &mut EnemyRegistryEntry { let pack_id = pack.id(); let pack_name = pack.name(); @@ -67,12 +67,12 @@ impl EnemyRegistry { } impl Index for EnemyRegistry { - type Output = (Arc, Vec); + type Output = (Arc, Vec); fn index( &self, index: SelectedEnemyPackIndex, - ) -> &(Arc, Vec) { + ) -> &(Arc, Vec) { &self.deref()[index.0] } } @@ -80,13 +80,13 @@ impl Index for EnemyRegistry { /// Container for enemy registry entries. #[derive(Clone, Debug)] pub struct EnemyRegistryEntry { - pub enemy: Arc, + pub enemy: Arc, pub tags: SmallVec<[SmolStr; 3]>, } impl EnemyRegistryEntry { /// Create a new entry for an enemy. - pub fn new(enemy: E) -> EnemyRegistryEntry { + pub fn new(enemy: E) -> EnemyRegistryEntry { EnemyRegistryEntry { enemy: Arc::new(enemy), tags: SmallVec::new() } } } @@ -107,9 +107,9 @@ impl EnemyRegistryEntry { } impl Deref for EnemyRegistryEntry { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Arc { + fn deref(&self) -> &Arc { &self.enemy } } diff --git a/game/src/enemy/resources.rs b/game/src/enemy/resources.rs index 0f3c3bf..caab40c 100644 --- a/game/src/enemy/resources.rs +++ b/game/src/enemy/resources.rs @@ -8,7 +8,7 @@ pub struct SelectedEnemyPackIndex(pub usize); /// Resource for the selected enemy pack. #[derive(Clone, Debug, Deref, Resource)] -pub struct SelectedEnemyPack(pub (Arc, Vec)); +pub struct SelectedEnemyPack(pub (Arc, Vec)); /// Resource for counting spawned enemies. @@ -58,7 +58,7 @@ pub struct EnemySpawn { /// Delay for the first spawn. pub delay: Timer, /// Enemy to spawn. - pub enemy: Arc, + pub enemy: Arc, /// Group size. pub count: u32, /// Optional spawn interval within the group. @@ -90,7 +90,7 @@ pub struct EnemySpawn { impl EnemySpawn { /// Creates a new enemy spawn. - pub fn new(delay: Duration, enemy: &Arc) -> EnemySpawn { + pub fn new(delay: Duration, enemy: &Arc) -> EnemySpawn { EnemySpawn { delay: Timer::new(delay, TimerMode::Once), enemy: Arc::clone(enemy), diff --git a/game/src/inventory/resources.rs b/game/src/inventory/resources.rs index 75ef28b..7ca1d03 100644 --- a/game/src/inventory/resources.rs +++ b/game/src/inventory/resources.rs @@ -4,21 +4,21 @@ use crate::prelude::*; /// Container for the items in the inventory. #[derive(Debug)] pub struct ItemInstance { - pub item: Box, + pub item: Box, pub entity: Option, } impl ItemInstance { /// Creates a new item instance. - pub fn new(item: impl Item) -> ItemInstance { + pub fn new(item: impl IItem) -> ItemInstance { ItemInstance { item: Box::new(item), entity: None } } } impl Deref for ItemInstance { - type Target = Box; + type Target = Box; - fn deref(&self) -> &Box { + fn deref(&self) -> &Box { &self.item } } diff --git a/game/src/items/interfaces.rs b/game/src/items/interfaces.rs index 0fa3da9..d473cc4 100644 --- a/game/src/items/interfaces.rs +++ b/game/src/items/interfaces.rs @@ -2,7 +2,7 @@ use crate::prelude::*; /// Interface for items. -pub trait Item: Debug + Send + Sync + 'static { +pub trait IItem: Debug + Send + Sync + 'static { /// Gets the unique identifier of the item. fn id(&self) -> SmolStr; /// Gets the name of the item. diff --git a/game/src/items/registry.rs b/game/src/items/registry.rs index a70e670..41d59c0 100644 --- a/game/src/items/registry.rs +++ b/game/src/items/registry.rs @@ -16,7 +16,7 @@ impl ItemRegistry { impl ItemRegistry { /// Registers an item to the item registry. - pub fn register(&mut self, item: impl Item) -> &mut ItemRegistryEntry { + pub fn register(&mut self, item: impl IItem) -> &mut ItemRegistryEntry { let id = item.id(); if self.iter().any(|entry| entry.id() == id) { log::warn!("tried to register {:?} to item registry again", item.id()); @@ -31,13 +31,13 @@ impl ItemRegistry { /// Container for item registry entries. #[derive(Debug)] pub struct ItemRegistryEntry { - pub item: Arc, + pub item: Arc, pub tags: SmallVec<[SmolStr; 3]>, } impl ItemRegistryEntry { /// Create a new entry for an item. - pub fn new(item: I) -> ItemRegistryEntry { + pub fn new(item: I) -> ItemRegistryEntry { ItemRegistryEntry { item: Arc::new(item), tags: SmallVec::new() } } } @@ -51,9 +51,9 @@ impl ItemRegistryEntry { } impl Deref for ItemRegistryEntry { - type Target = Arc; + type Target = Arc; - fn deref(&self) -> &Arc { + fn deref(&self) -> &Arc { &self.item } } diff --git a/game/src/mode/conditions.rs b/game/src/mode/conditions.rs index d4904ef..897d66a 100644 --- a/game/src/mode/conditions.rs +++ b/game/src/mode/conditions.rs @@ -2,6 +2,6 @@ use crate::prelude::*; /// Run condition for game mode. -pub fn in_game_mode(mode: Option>>) -> bool { +pub fn in_game_mode(mode: Option>>) -> bool { mode.is_some() } diff --git a/game/src/mode/interfaces.rs b/game/src/mode/interfaces.rs index ef5af88..7c7ddb7 100644 --- a/game/src/mode/interfaces.rs +++ b/game/src/mode/interfaces.rs @@ -2,7 +2,7 @@ use crate::prelude::*; /// Interface for game modes. -pub trait Mode: Debug + Send + Sync + 'static { +pub trait IGameMode: Debug + Send + Sync + 'static { /// Gets the unique identifier of the game mode. fn id(&self) -> SmolStr; /// Gets the name of the game mode. diff --git a/game/src/mode/registry.rs b/game/src/mode/registry.rs index d50e1d1..0074a23 100644 --- a/game/src/mode/registry.rs +++ b/game/src/mode/registry.rs @@ -5,7 +5,7 @@ pub static GAME_MODE_REGISTRY: Mutex = Mutex::new(GameModeRegi /// Container for game mode registry. #[derive(Default, Deref, DerefMut, Resource)] -pub struct GameModeRegistry(pub Vec>); +pub struct GameModeRegistry(pub Vec>); impl GameModeRegistry { /// Creates a new game mode registry. @@ -16,7 +16,7 @@ impl GameModeRegistry { impl GameModeRegistry { /// Registers a game mode to game mode registry. - pub fn register(&mut self, game_mode: impl Mode) { + pub fn register(&mut self, game_mode: impl IGameMode) { if self.iter().any(|existing_entry| existing_entry.id() == game_mode.id()) { log::warn!("tried to register {:?} to game mode registry again", game_mode.name()); } else { @@ -27,9 +27,9 @@ impl GameModeRegistry { } impl Index for GameModeRegistry { - type Output = Arc; + type Output = Arc; - fn index(&self, index: SelectedGameModeIndex) -> &Arc { + fn index(&self, index: SelectedGameModeIndex) -> &Arc { &self.deref()[index.0] } } diff --git a/game/src/mode/resources.rs b/game/src/mode/resources.rs index 85702ee..7f441b0 100644 --- a/game/src/mode/resources.rs +++ b/game/src/mode/resources.rs @@ -8,9 +8,9 @@ pub struct SelectedGameModeIndex(pub usize); /// Resource for the selected game mode. #[derive(Clone, Debug, Deref, Resource)] -pub struct SelectedGameMode(pub Arc); +pub struct SelectedGameMode(pub Arc); /// Resource for the current game mode. #[derive(Debug, Default, Deref, Reflect, Resource)] -pub struct GameMode(pub M); +pub struct GameMode(pub M); diff --git a/game/src/player/components.rs b/game/src/player/components.rs index 795f257..656fc67 100644 --- a/game/src/player/components.rs +++ b/game/src/player/components.rs @@ -32,7 +32,7 @@ pub struct DamagePlayerOnContact; /// Bundle for players. #[derive(Bundle, TypedBuilder)] -pub struct PlayerBundle { +pub struct PlayerBundle { pub player: P, pub mesh: MaterialMesh2dBundle, #[builder(setter(transform = @@ -43,7 +43,7 @@ pub struct PlayerBundle { pub input: InputManagerBundle, } -impl PlayerBundle

{ +impl PlayerBundle

{ /// Spawns the player. pub fn spawn<'w, 's, 'a>( self, diff --git a/game/src/player/interfaces.rs b/game/src/player/interfaces.rs index 0dd8c90..f5abddf 100644 --- a/game/src/player/interfaces.rs +++ b/game/src/player/interfaces.rs @@ -5,7 +5,7 @@ use crate::{ /// Interface for mythologies. -pub trait Mythology: Any + Debug + Send + Sync + 'static { +pub trait IMythology: Any + Debug + Send + Sync + 'static { /// Gets the unique identifier of the mythology. fn id(&self) -> SmolStr; /// Gets the name of the mythology. @@ -14,7 +14,7 @@ pub trait Mythology: Any + Debug + Send + Sync + 'static { /// Interface for players. -pub trait Playable: Debug + Send + Sync + 'static { +pub trait IPlayer: Debug + Send + Sync + 'static { /// Gets the unique identifier of the player. fn id(&self) -> SmolStr; /// Gets the name of the player. diff --git a/game/src/player/registry.rs b/game/src/player/registry.rs index 432b928..5401002 100644 --- a/game/src/player/registry.rs +++ b/game/src/player/registry.rs @@ -5,7 +5,7 @@ pub static PLAYER_REGISTRY: Mutex = Mutex::new(PlayerRegistry::n /// Container for player registry. #[derive(Default, Deref, DerefMut, Resource)] -pub struct PlayerRegistry(pub Vec<(Arc, Vec>)>); +pub struct PlayerRegistry(pub Vec<(Arc, Vec>)>); impl PlayerRegistry { /// Creates a new player registry. @@ -16,7 +16,7 @@ impl PlayerRegistry { impl PlayerRegistry { /// Registers a player to player registry. - pub fn register(&mut self, mythology: impl Mythology, player: impl Playable) { + pub fn register(&mut self, mythology: impl IMythology, player: impl IPlayer) { let mythology_name = mythology.name(); let player_name = player.name(); @@ -64,17 +64,17 @@ impl PlayerRegistry { } impl Index for PlayerRegistry { - type Output = (Arc, Vec>); + type Output = (Arc, Vec>); - fn index(&self, index: usize) -> &(Arc, Vec>) { + fn index(&self, index: usize) -> &(Arc, Vec>) { &self.deref()[index] } } impl Index for PlayerRegistry { - type Output = Arc; + type Output = Arc; - fn index(&self, index: SelectedPlayerIndex) -> &Arc { + fn index(&self, index: SelectedPlayerIndex) -> &Arc { let (_, players) = &self.deref()[index.mythology_index]; &players.deref()[index.player_index] } diff --git a/game/src/player/resources.rs b/game/src/player/resources.rs index d9a8a41..ba6f65f 100644 --- a/game/src/player/resources.rs +++ b/game/src/player/resources.rs @@ -10,4 +10,4 @@ pub struct SelectedPlayerIndex { /// Resource for the selected player. #[derive(Clone, Debug, Deref, Resource)] -pub struct SelectedPlayer(pub Arc); +pub struct SelectedPlayer(pub Arc); diff --git a/items/greek/src/bident_of_hades.rs b/items/greek/src/bident_of_hades.rs index 57cde0c..1015297 100644 --- a/items/greek/src/bident_of_hades.rs +++ b/items/greek/src/bident_of_hades.rs @@ -7,7 +7,7 @@ use { #[derive(Clone, Component, Debug, Reflect)] pub struct BidentOfHades; -impl Item for BidentOfHades { +impl IItem for BidentOfHades { fn id(&self) -> SmolStr { "bident-of-hades".into() } diff --git a/items/greek/src/bow_of_artemis.rs b/items/greek/src/bow_of_artemis.rs index f5cd933..dfa6712 100644 --- a/items/greek/src/bow_of_artemis.rs +++ b/items/greek/src/bow_of_artemis.rs @@ -22,7 +22,7 @@ pub const BASE_PROJECTILE_SPEED: f32 = 250.00; #[derive(Clone, Component, Debug, Reflect)] pub struct BowOfArtemis; -impl Item for BowOfArtemis { +impl IItem for BowOfArtemis { fn id(&self) -> SmolStr { "bow-of-artemis".into() } diff --git a/modes/survival/src/mode.rs b/modes/survival/src/mode.rs index 616a94c..d92c62b 100644 --- a/modes/survival/src/mode.rs +++ b/modes/survival/src/mode.rs @@ -7,7 +7,7 @@ use mythmallow::{ #[derive(Debug, Default, Reflect, Resource)] pub struct Survival; -impl Mode for Survival { +impl IGameMode for Survival { fn id(&self) -> SmolStr { "survival".into() } diff --git a/players/greek/src/artemis.rs b/players/greek/src/artemis.rs index 2071141..254245e 100644 --- a/players/greek/src/artemis.rs +++ b/players/greek/src/artemis.rs @@ -14,7 +14,7 @@ pub const COLOR: Color = Color::GREEN; #[derive(Clone, Component, Debug, Reflect)] pub struct Artemis; -impl Playable for Artemis { +impl IPlayer for Artemis { fn id(&self) -> SmolStr { "artemis".into() } diff --git a/players/greek/src/hades.rs b/players/greek/src/hades.rs index c09ea8e..c880709 100644 --- a/players/greek/src/hades.rs +++ b/players/greek/src/hades.rs @@ -14,7 +14,7 @@ pub const COLOR: Color = Color::BLACK; #[derive(Clone, Component, Debug, Reflect)] pub struct Hades; -impl Playable for Hades { +impl IPlayer for Hades { fn id(&self) -> SmolStr { "hades".into() } diff --git a/players/greek/src/mythology.rs b/players/greek/src/mythology.rs index 59080ae..3ea27b9 100644 --- a/players/greek/src/mythology.rs +++ b/players/greek/src/mythology.rs @@ -4,7 +4,7 @@ use mythmallow::prelude::*; #[derive(Debug)] pub struct GreekMythology; -impl Mythology for GreekMythology { +impl IMythology for GreekMythology { fn id(&self) -> SmolStr { "greek".into() } From f4356b463ab24d82913cf87a1dd93f3055b03ab6 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 20:11:30 +0300 Subject: [PATCH 18/38] refactor: add getters for number of items in registries --- game/src/enemy/registry.rs | 12 ++++++++++++ game/src/items/registry.rs | 7 +++++++ game/src/mode/registry.rs | 7 +++++++ game/src/player/registry.rs | 10 ++++++++++ src/main.rs | 14 ++++++-------- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/game/src/enemy/registry.rs b/game/src/enemy/registry.rs index 3559de4..6f2ed59 100644 --- a/game/src/enemy/registry.rs +++ b/game/src/enemy/registry.rs @@ -66,6 +66,18 @@ impl EnemyRegistry { } } +impl EnemyRegistry { + /// Gets the number of enemy packs in the enemy registry. + pub fn number_of_enemy_packs(&self) -> usize { + self.0.len() + } + + /// Gets the number of enemies in the enemy registry. + pub fn number_of_enemies(&self) -> usize { + self.0.iter().map(|(_, enemies)| enemies.len()).sum() + } +} + impl Index for EnemyRegistry { type Output = (Arc, Vec); diff --git a/game/src/items/registry.rs b/game/src/items/registry.rs index 41d59c0..a306886 100644 --- a/game/src/items/registry.rs +++ b/game/src/items/registry.rs @@ -28,6 +28,13 @@ impl ItemRegistry { } } +impl ItemRegistry { + /// Gets the number of items in the item registry. + pub fn number_of_items(&self) -> usize { + self.0.len() + } +} + /// Container for item registry entries. #[derive(Debug)] pub struct ItemRegistryEntry { diff --git a/game/src/mode/registry.rs b/game/src/mode/registry.rs index 0074a23..292ec25 100644 --- a/game/src/mode/registry.rs +++ b/game/src/mode/registry.rs @@ -26,6 +26,13 @@ impl GameModeRegistry { } } +impl GameModeRegistry { + /// Gets the number of game modes in the game mode registry. + pub fn number_of_game_modes(&self) -> usize { + self.0.len() + } +} + impl Index for GameModeRegistry { type Output = Arc; diff --git a/game/src/player/registry.rs b/game/src/player/registry.rs index 5401002..d97c450 100644 --- a/game/src/player/registry.rs +++ b/game/src/player/registry.rs @@ -49,6 +49,16 @@ impl PlayerRegistry { } impl PlayerRegistry { + /// Gets the number of mythologies in the player registry. + pub fn number_of_mythologies(&self) -> usize { + self.0.len() + } + + /// Gets the number of players in the player registry. + pub fn number_of_players(&self) -> usize { + self.0.iter().map(|(_, players)| players.len()).sum() + } + /// Finds the player from it's id. pub fn find(&self, id: impl AsRef) -> Option { let id = id.as_ref(); diff --git a/src/main.rs b/src/main.rs index a1db55b..04d112a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ fn main() { app.add_plugins(SurvivalModePlugin); { let game_mode_registry = GAME_MODE_REGISTRY.lock().unwrap(); - let number_of_game_modes = game_mode_registry.len(); + let number_of_game_modes = game_mode_registry.number_of_game_modes(); log::info!( "{} game mode{} {} registered", number_of_game_modes, @@ -58,7 +58,7 @@ fn main() { app.add_plugins(GreekItemsPlugin); { let item_registry = ITEM_REGISTRY.lock().unwrap(); - let number_of_items = item_registry.len(); + let number_of_items = item_registry.number_of_items(); log::info!( "{} item{} {} registered", number_of_items, @@ -71,9 +71,8 @@ fn main() { app.add_plugins(GreekPlayersPlugin); { let player_registry = PLAYER_REGISTRY.lock().unwrap(); - let number_of_mythologies = player_registry.len(); - let number_of_players = - player_registry.iter().map(|(_, players)| players.len()).sum::(); + let number_of_mythologies = player_registry.number_of_mythologies(); + let number_of_players = player_registry.number_of_players(); log::info!( "{} player{} {} registered across {} mytholog{}", number_of_players, @@ -88,9 +87,8 @@ fn main() { app.add_plugins(SweetEnemiesPlugin); { let enemy_registry = ENEMY_REGISTRY.lock().unwrap(); - let number_of_enemy_packs = enemy_registry.len(); - let number_of_enemies = - enemy_registry.iter().map(|(_, enemies)| enemies.len()).sum::(); + let number_of_enemy_packs = enemy_registry.number_of_enemy_packs(); + let number_of_enemies = enemy_registry.number_of_enemies(); log::info!( "{} enem{} {} registered across {} enemy pack{}", number_of_enemies, From 6a19849c5a457fff493c4482097878978977440d Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 21:31:07 +0300 Subject: [PATCH 19/38] fix: check for development feature instead of debug assertions --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 04d112a..fc08199 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ // Disable spawning command prompt on Windows in release mode. -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +#![cfg_attr(not(feature = "development"), windows_subsystem = "windows")] use { mythmallow_enemies_sweet::prelude::*, @@ -139,7 +139,7 @@ fn initialize(app: &mut App, args: &Args) { // Add persistent windows plugin. app.add_plugins(PersistentWindowsPlugin); - #[cfg(debug_assertions)] + #[cfg(feature = "development")] { // Setup exiting the application with CTRL+Q in development mode. fn exit_with_ctrl_q( From 6a2c809bdea4764608a1d9c4927d0238aedad195 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 21:34:15 +0300 Subject: [PATCH 20/38] feat: physics debugging in development mode --- game/src/configuration/resources.rs | 11 +++++++- game/src/input/actions/global.rs | 17 ++++++++++--- game/src/input/plugin.rs | 12 ++++++--- game/src/input/systems.rs | 39 +++++++++++++++++++++-------- game/src/physics/plugin.rs | 14 ++++++++++- 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/game/src/configuration/resources.rs b/game/src/configuration/resources.rs index 65b9d37..c492980 100644 --- a/game/src/configuration/resources.rs +++ b/game/src/configuration/resources.rs @@ -220,6 +220,9 @@ impl Args { pub struct GeneralSettings { pub pause_on_losing_focus: bool, pub show_diagnostics_overlay: bool, + + #[cfg(feature = "development")] + pub debug_physics: bool, } impl GeneralSettings { @@ -250,7 +253,13 @@ impl GeneralSettings { impl Default for GeneralSettings { fn default() -> GeneralSettings { - GeneralSettings { pause_on_losing_focus: true, show_diagnostics_overlay: false } + GeneralSettings { + pause_on_losing_focus: true, + show_diagnostics_overlay: false, + + #[cfg(feature = "development")] + debug_physics: false, + } } } diff --git a/game/src/input/actions/global.rs b/game/src/input/actions/global.rs index 56e9c5c..e89b380 100644 --- a/game/src/input/actions/global.rs +++ b/game/src/input/actions/global.rs @@ -5,6 +5,9 @@ use crate::prelude::*; pub enum GlobalAction { ToggleFullscreen, ToggleDiagnosticsOverlay, + + #[cfg(feature = "development")] + TogglePhysicsDebug, } impl GlobalAction { @@ -14,10 +17,16 @@ impl GlobalAction { app.add_plugins(InputManagerPlugin::::default()); // Create the input map. - let input_map = InputMap::new([ - (KeyCode::F11, GlobalAction::ToggleFullscreen), - (KeyCode::F10, GlobalAction::ToggleDiagnosticsOverlay), - ]); + let mut input_map = InputMap::default(); + + input_map.insert(KeyCode::F11, GlobalAction::ToggleFullscreen); + input_map.insert(KeyCode::F10, GlobalAction::ToggleDiagnosticsOverlay); + + #[cfg(feature = "development")] + input_map.insert( + UserInput::chord([KeyCode::ControlLeft, KeyCode::P]), + GlobalAction::TogglePhysicsDebug, + ); // Insert the input map resource. app.insert_resource(input_map); diff --git a/game/src/input/plugin.rs b/game/src/input/plugin.rs index 93d3452..8de05f2 100644 --- a/game/src/input/plugin.rs +++ b/game/src/input/plugin.rs @@ -17,8 +17,14 @@ impl Plugin for InputPlugin { GameOverMenuAction::setup(app); // Add systems. - app.add_systems(Update, toggle_fullscreen); - app.add_systems(Update, toggle_diagnostics_overlay); - app.add_systems(Update, pause_on_losing_focus.in_set(GameplaySystems::Input)); + { + app.add_systems(Update, pause_on_losing_focus.in_set(GameplaySystems::Input)); + + app.add_systems(Update, toggle_fullscreen); + app.add_systems(Update, toggle_diagnostics_overlay); + + #[cfg(feature = "development")] + app.add_systems(Update, toggle_physics_debug); + } } } diff --git a/game/src/input/systems.rs b/game/src/input/systems.rs index fd03a44..50ada5b 100644 --- a/game/src/input/systems.rs +++ b/game/src/input/systems.rs @@ -1,6 +1,21 @@ use crate::prelude::*; +/// Pauses the game when the application loses it's focus. +pub fn pause_on_losing_focus( + mut window_focused_reader: EventReader, + general_settings: Res>, + mut next_game_state: ResMut>, +) { + for event in window_focused_reader.read() { + if !event.focused && general_settings.pause_on_losing_focus { + next_game_state.set(GameState::Paused); + break; + } + } +} + + /// Toggles the window mode between fullscreen and windowed. #[cfg(feature = "native")] pub fn toggle_fullscreen( @@ -70,16 +85,20 @@ pub fn toggle_diagnostics_overlay( } -/// Pauses the game when the application loses it's focus. -pub fn pause_on_losing_focus( - mut window_focused_reader: EventReader, - general_settings: Res>, - mut next_game_state: ResMut>, +/// Toggles physics debug. +#[cfg(feature = "development")] +pub fn toggle_physics_debug( + global_action_state: Res>, + mut general_settings: ResMut>, + mut physics_debug_config: ResMut, ) { - for event in window_focused_reader.read() { - if !event.focused && general_settings.pause_on_losing_focus { - next_game_state.set(GameState::Paused); - break; - } + if global_action_state.just_pressed(GlobalAction::TogglePhysicsDebug) { + general_settings + .update(|general_settings| { + general_settings.debug_physics = !general_settings.debug_physics; + }) + .ok(); + + physics_debug_config.enabled = general_settings.debug_physics; } } diff --git a/game/src/physics/plugin.rs b/game/src/physics/plugin.rs index e557819..7a56b8a 100644 --- a/game/src/physics/plugin.rs +++ b/game/src/physics/plugin.rs @@ -13,7 +13,19 @@ impl Plugin for PhysicsPlugin { // Setup physics. app.insert_resource(Gravity::ZERO); - app.add_plugins(XpbdPlugin::new(PostUpdate)); + app.add_plugins(XpbdPlugin::default()); + + // Setup physics debug in development mode. + #[cfg(feature = "development")] + { + let general_settings = app.world.resource::>(); + app.insert_resource(PhysicsDebugConfig { + enabled: general_settings.debug_physics, + ..default() + }); + + app.add_plugins(PhysicsDebugPlugin::default()); + } // Pause physics in startup. app.world.resource_mut::>().pause(); From af13f96aaa60f27de4349b4fb0d3439e468022f1 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 21:35:48 +0300 Subject: [PATCH 21/38] feat: spawn map bounds automatically --- game/src/map/constants.rs | 2 ++ game/src/map/mod.rs | 1 + game/src/map/plugin.rs | 1 + game/src/map/systems.rs | 60 ++++++++++++++++++++++++++++++++++- modes/survival/src/systems.rs | 41 ------------------------ 5 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 game/src/map/constants.rs diff --git a/game/src/map/constants.rs b/game/src/map/constants.rs new file mode 100644 index 0000000..73a2cc2 --- /dev/null +++ b/game/src/map/constants.rs @@ -0,0 +1,2 @@ +/// Thickness of map bounds. +pub const BOUND_THICKNESS: f32 = 50.00; diff --git a/game/src/map/mod.rs b/game/src/map/mod.rs index 81726da..c99e30f 100644 --- a/game/src/map/mod.rs +++ b/game/src/map/mod.rs @@ -1,4 +1,5 @@ pub mod components; +pub mod constants; pub mod plugin; pub mod resources; pub mod systems; diff --git a/game/src/map/plugin.rs b/game/src/map/plugin.rs index 6b2d9f8..ae8504a 100644 --- a/game/src/map/plugin.rs +++ b/game/src/map/plugin.rs @@ -16,6 +16,7 @@ impl Plugin for MapPlugin { app.register_type::(); // Add systems. + app.add_systems(OnExit(GameState::Loading), spawn_map_bounds); app.add_systems(OnEnter(GameState::Won), despawn_map); app.add_systems(OnEnter(GameState::Over), despawn_map); app.add_systems(OnEnter(GameState::Restart), despawn_map.in_set(RestartSystems::Map)); diff --git a/game/src/map/systems.rs b/game/src/map/systems.rs index dc68e4f..f78db95 100644 --- a/game/src/map/systems.rs +++ b/game/src/map/systems.rs @@ -1,4 +1,62 @@ -use crate::prelude::*; +use crate::{ + map::constants::*, + prelude::*, +}; + + +/// Spawns the bounds of the map. +pub fn spawn_map_bounds( + mut commands: Commands, + map_bounds: Res, + map_query: Query>, +) { + let mut map = match map_query.get_single() { + Ok(map_entity) => commands.entity(map_entity), + Err(_) => return, + }; + map.with_children(|parent| { + let layers = CollisionLayers::new( + [Layer::MapBound], + [Layer::Player, Layer::Enemy, Layer::Projectile], + ); + + let x_length = (map_bounds.x_max - map_bounds.x_min) + (4.00 * BOUND_THICKNESS); + let y_length = map_bounds.y_max - map_bounds.y_min; + + parent.spawn(( + Name::new("Left Bound"), + MapBound, + RigidBody::Static, + Collider::cuboid(2.00 * BOUND_THICKNESS, y_length), + layers, + Position(Vector::X * (map_bounds.x_min - BOUND_THICKNESS)), + )); + parent.spawn(( + Name::new("Top Bound"), + MapBound, + RigidBody::Static, + Collider::cuboid(x_length, 2.00 * BOUND_THICKNESS), + layers, + Position(Vector::Y * (map_bounds.y_max + BOUND_THICKNESS)), + )); + parent.spawn(( + Name::new("Right Bound"), + MapBound, + RigidBody::Static, + Collider::cuboid(2.00 * BOUND_THICKNESS, y_length), + layers, + Position(Vector::X * (map_bounds.x_max + BOUND_THICKNESS)), + )); + parent.spawn(( + Name::new("Bottom Bound"), + MapBound, + RigidBody::Static, + Collider::cuboid(x_length, 2.00 * BOUND_THICKNESS), + layers, + Position(Vector::Y * (map_bounds.y_min - BOUND_THICKNESS)), + )); + }); +} /// Despawns the map. diff --git a/modes/survival/src/systems.rs b/modes/survival/src/systems.rs index aceea78..595ce96 100644 --- a/modes/survival/src/systems.rs +++ b/modes/survival/src/systems.rs @@ -27,47 +27,6 @@ pub fn spawn_map(mut commands: Commands) { y_max: MAP_BOUND, }); commands.spawn((Name::new("Map"), Map, SpatialBundle::default())).with_children(|parent| { - // Define physics layers of the walls. - let layers = CollisionLayers::new( - [Layer::MapBound], - [Layer::Player, Layer::Enemy, Layer::Projectile], - ); - // Spawn left wall. - parent.spawn(( - Name::new("Left Wall"), - MapBound, - RigidBody::Static, - Collider::cuboid(50.0, MAP_BOUND * 2.0), - layers, - Position(Vector::NEG_X * (MAP_BOUND + 25.0)), - )); - // Spawn top wall. - parent.spawn(( - Name::new("Top Wall"), - MapBound, - RigidBody::Static, - Collider::cuboid(MAP_BOUND * 2.0, 50.0), - layers, - Position(Vector::Y * (MAP_BOUND + 25.0)), - )); - // Spawn right wall. - parent.spawn(( - Name::new("Right Wall"), - MapBound, - RigidBody::Static, - Collider::cuboid(50.0, MAP_BOUND * 2.0), - layers, - Position(Vector::X * (MAP_BOUND + 25.0)), - )); - // Spawn bottom wall. - parent.spawn(( - Name::new("Bottom Wall"), - MapBound, - RigidBody::Static, - Collider::cuboid(MAP_BOUND * 2.0, 50.0), - layers, - Position(Vector::NEG_Y * (MAP_BOUND + 25.0)), - )); // Spawn horizontal lines. for i in 0..=MAP_SIZE { parent.spawn(( From 10046b9ddd017febfa4b3c0e7947303056a830f0 Mon Sep 17 00:00:00 2001 From: Umut Date: Tue, 2 Jan 2024 22:01:28 +0300 Subject: [PATCH 22/38] refactor: improve survival mode --- modes/survival/src/constants.rs | 21 ++++++++++++++---- modes/survival/src/mode.rs | 3 ++- modes/survival/src/plugin.rs | 4 ++-- modes/survival/src/resources.rs | 15 +++++++++++++ modes/survival/src/systems.rs | 38 ++++++++++++++++----------------- 5 files changed, 54 insertions(+), 27 deletions(-) diff --git a/modes/survival/src/constants.rs b/modes/survival/src/constants.rs index a8f2420..f9dd3cd 100644 --- a/modes/survival/src/constants.rs +++ b/modes/survival/src/constants.rs @@ -1,13 +1,26 @@ -/// Size of the map. Value of 10 means the map will be a 10x10 grid of squares. -pub const MAP_SIZE: i32 = 10; +use mythmallow::prelude::*; +/// Size of the grid. Value of 10 means the map will be a 10x10 grid of squares. +pub const GRID_SIZE: i32 = 10; + /// Amount of space between grid elements. pub const GRID_SPACING: f32 = 50.0; /// Thickness of grid elements. pub const GRID_WIDTH: f32 = 2.0; +/// Color of the grid. +pub const GRID_COLOR: Color = Color::rgb(0.27, 0.27, 0.27); + + +/// Size of the map. +pub const MAP_SIZE: f32 = (GRID_SIZE as f32) * GRID_SPACING; -/// Bound of the map in terms of world coordinates. -pub const MAP_BOUND: f32 = (MAP_SIZE as f32) * GRID_SPACING / 2.00; +/// Bounds of the map. +pub const MAP_BOUNDS: MapBounds = MapBounds { + x_min: -(MAP_SIZE / 2.00), + x_max: (MAP_SIZE / 2.00), + y_min: -(MAP_SIZE / 2.00), + y_max: (MAP_SIZE / 2.00), +}; diff --git a/modes/survival/src/mode.rs b/modes/survival/src/mode.rs index d92c62b..ce731d3 100644 --- a/modes/survival/src/mode.rs +++ b/modes/survival/src/mode.rs @@ -3,8 +3,9 @@ use mythmallow::{ prelude::*, }; -/// Survival game mode. +/// Resource for "Survival" game mode. #[derive(Debug, Default, Reflect, Resource)] +#[reflect(Resource)] pub struct Survival; impl IGameMode for Survival { diff --git a/modes/survival/src/plugin.rs b/modes/survival/src/plugin.rs index 2beb168..cb6b455 100644 --- a/modes/survival/src/plugin.rs +++ b/modes/survival/src/plugin.rs @@ -6,7 +6,7 @@ use { mythmallow::prelude::*, }; -/// Plugin for managing the "Survival" game mode. +/// Plugin for managing "Survival" game mode. pub struct SurvivalModePlugin; impl Plugin for SurvivalModePlugin { @@ -40,7 +40,7 @@ impl Plugin for SurvivalModePlugin { // Add gameplay systems. app.add_systems( - Update, + PreUpdate, tick.in_set(GameplaySystems::GameMode).run_if(in_game_mode::), ); diff --git a/modes/survival/src/resources.rs b/modes/survival/src/resources.rs index d9f9840..668ea49 100644 --- a/modes/survival/src/resources.rs +++ b/modes/survival/src/resources.rs @@ -3,6 +3,7 @@ use mythmallow::prelude::*; /// Resource for the current wave. #[derive(Debug, Deref, DerefMut, Reflect, Resource)] +#[reflect(Resource)] pub struct CurrentWave(pub u8); impl Default for CurrentWave { @@ -14,4 +15,18 @@ impl Default for CurrentWave { /// Resource for the remaining time to complete the current wave. #[derive(Debug, Deref, DerefMut, Reflect, Resource)] +#[reflect(Resource)] pub struct WaveTimer(pub Timer); + +impl WaveTimer { + /// Creates a new wave timer. + pub fn new(duration: Duration) -> WaveTimer { + WaveTimer(Timer::new(duration, TimerMode::Once)) + } +} + +impl Default for WaveTimer { + fn default() -> WaveTimer { + WaveTimer(Timer::new(Duration::from_secs(60), TimerMode::Once)) + } +} diff --git a/modes/survival/src/systems.rs b/modes/survival/src/systems.rs index 595ce96..198801f 100644 --- a/modes/survival/src/systems.rs +++ b/modes/survival/src/systems.rs @@ -12,34 +12,29 @@ pub fn initialize(mut commands: Commands) { commands.insert_resource(CurrentWave(1)); } + /// Loads the current wave. pub fn load(mut commands: Commands) { - commands.insert_resource(WaveTimer(Timer::new(Duration::from_secs(5), TimerMode::Once))); + commands.insert_resource(WaveTimer::new(Duration::from_secs(10))); } - /// Spawns the map. pub fn spawn_map(mut commands: Commands) { - commands.insert_resource(MapBounds { - x_min: -MAP_BOUND, - x_max: MAP_BOUND, - y_min: -MAP_BOUND, - y_max: MAP_BOUND, - }); + commands.insert_resource(MAP_BOUNDS); commands.spawn((Name::new("Map"), Map, SpatialBundle::default())).with_children(|parent| { // Spawn horizontal lines. - for i in 0..=MAP_SIZE { + for i in 0..=GRID_SIZE { parent.spawn(( Name::new(format!("Horizontal Line {}", i + 1)), SpriteBundle { transform: Transform::from_translation(Vec3::new( - 0.0, - (((MAP_SIZE as f32) / 2.0) - (i as f32)) * GRID_SPACING, - 0.0, + 0.00, + (((GRID_SIZE as f32) / 2.00) - (i as f32)) * GRID_SPACING, + 0.00, )), sprite: Sprite { - color: Color::rgb(0.27, 0.27, 0.27), - custom_size: Some(Vec2::new(MAP_SIZE as f32 * GRID_SPACING, GRID_WIDTH)), + color: GRID_COLOR, + custom_size: Some(Vec2::new(GRID_SIZE as f32 * GRID_SPACING, GRID_WIDTH)), ..default() }, ..default() @@ -47,18 +42,18 @@ pub fn spawn_map(mut commands: Commands) { )); } // Spawn vertical lines. - for i in 0..=MAP_SIZE { + for i in 0..=GRID_SIZE { parent.spawn(( Name::new(format!("Vertical Line {}", i + 1)), SpriteBundle { transform: Transform::from_translation(Vec3::new( - ((i as f32) - ((MAP_SIZE as f32) / 2.0)) * GRID_SPACING, - 0.0, - 0.0, + ((i as f32) - ((GRID_SIZE as f32) / 2.00)) * GRID_SPACING, + 0.00, + 0.00, )), sprite: Sprite { - color: Color::rgb(0.27, 0.27, 0.27), - custom_size: Some(Vec2::new(GRID_WIDTH, MAP_SIZE as f32 * GRID_SPACING)), + color: GRID_COLOR, + custom_size: Some(Vec2::new(GRID_WIDTH, GRID_SIZE as f32 * GRID_SPACING)), ..default() }, ..default() @@ -68,6 +63,7 @@ pub fn spawn_map(mut commands: Commands) { }); } + /// Ticks wave timer and wins the current wave when wave timer is finished. pub fn tick( time: Res