diff --git a/.gitignore b/.gitignore index 4a77b82..47e55f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .*.swp +assets/sprites/orig diff --git a/assets/fonts/Kottke Silkscreen License.txt b/assets/fonts/Kottke Silkscreen License.txt new file mode 100644 index 0000000..a3d852e --- /dev/null +++ b/assets/fonts/Kottke Silkscreen License.txt @@ -0,0 +1,25 @@ +Thank you for downloading Silkscreen, a type family for your Web graphics +by Jason Kottke (jason@kottke.org). + +To install the Silkscreen type family, unzip this file and drag the files +into the Fonts folder in the Control Panel. + +If you encounter any problems in using this font, please email me and I'll +see if I can try and fix it. Please note that I can't help you with any +installation issues. Please consult your system's help files for assistance. + +This font is free for personal and corporate use and may be redistributed in +this unmodified form on your Web site. I would ask that you not modify and +then redistribute this font...although you may modify it for your own +personal use. If you really like this font and use it often, feel free to +mail me (e- or snail mail) some small token of your appreciation. A URL +of your work using Silkscreen would be appreciated as well. + +All future bug fixes, updates, and additions to the Silkscreen type family +will be available on my Web site at the following URL: + +http://www.kottke.org/plus/type/silkscreen/index.html + +Again, thanks for downloading Silkscreen. Enjoy! + +-jason \ No newline at end of file diff --git a/assets/fonts/slkscr.ttf b/assets/fonts/slkscr.ttf new file mode 100644 index 0000000..e2dd974 Binary files /dev/null and b/assets/fonts/slkscr.ttf differ diff --git a/assets/fonts/slkscrb.ttf b/assets/fonts/slkscrb.ttf new file mode 100644 index 0000000..ae4425d Binary files /dev/null and b/assets/fonts/slkscrb.ttf differ diff --git a/assets/fonts/slkscre.ttf b/assets/fonts/slkscre.ttf new file mode 100644 index 0000000..1e2be59 Binary files /dev/null and b/assets/fonts/slkscre.ttf differ diff --git a/assets/fonts/slkscreb.ttf b/assets/fonts/slkscreb.ttf new file mode 100644 index 0000000..cd68f57 Binary files /dev/null and b/assets/fonts/slkscreb.ttf differ diff --git a/assets/sprites/field_ichi_1.ron b/assets/sprites/field_ichi_1.ron index c6201c3..9982a56 100644 --- a/assets/sprites/field_ichi_1.ron +++ b/assets/sprites/field_ichi_1.ron @@ -76,7 +76,7 @@ Prefab( // Transform // TODO: Can I live without this? I want to set it dynamically. transform: ( - translation: (128.0, 128.0, 0.0), + translation: (128.0, 216.0, 0.0), ), ), // AnimationSetPrefab @@ -132,8 +132,8 @@ Prefab( ), // Collide a square around. collision_box: ( - upper_left_distance: [-8.0, 8.0], - lower_right_distance: [8.0, -8.0], + upper_left_distance: [-6.0, 6.0], + lower_right_distance: [6.0, -6.0], ), movement_state: ( velocity: [0.0, 0.0], @@ -146,6 +146,7 @@ Prefab( speed: 48.0, kick_strength: 256.0, push_strength: 96.0, + side: UpperSide, ), human: Human, ), @@ -160,8 +161,8 @@ Prefab( ), ), collision_box: ( - upper_left_distance: [-25.0, 4.0], - lower_right_distance: [-21.0, -4.0], + upper_left_distance: [-31.0, 4.0], + lower_right_distance: [-27.0, -4.0], ), animation_set: ( animations: [], @@ -178,8 +179,8 @@ Prefab( ), ), collision_box: ( - upper_left_distance: [-25.0, 4.0], - lower_right_distance: [-21.0, -4.0], + upper_left_distance: [-31.0, 4.0], + lower_right_distance: [-27.0, -4.0], ), animation_set: ( animations: [], @@ -196,8 +197,8 @@ Prefab( ), ), collision_box: ( - upper_left_distance: [21.0, 4.0], - lower_right_distance: [25.0, -4.0], + upper_left_distance: [27.0, 4.0], + lower_right_distance: [31.0, -4.0], ), animation_set: ( animations: [], @@ -214,8 +215,8 @@ Prefab( ), ), collision_box: ( - upper_left_distance: [21.0, 4.0], - lower_right_distance: [25.0, -4.0], + upper_left_distance: [27.0, 4.0], + lower_right_distance: [31.0, -4.0], ), animation_set: ( animations: [], @@ -238,7 +239,13 @@ Prefab( animation_set: ( animations: [], ), - extras: StaticData, + collision_box: ( + upper_left_distance: [-26.0, 2.0], + lower_right_distance: [26.0, -6.0], + ), + extras: NetData( + net: Net(side: LowerSide), + ), ), ), // UpperNet @@ -256,7 +263,42 @@ Prefab( animation_set: ( animations: [], ), - extras: StaticData, + collision_box: ( + upper_left_distance: [-26.0, 6.0], + lower_right_distance: [26.0, -2.0], + ), + extras: NetData( + net: Net(side: UpperSide), + ), + ), + ), + // Ball + PrefabEntity( + data: ( + sprite_scene: ( + render: ( + sheet: "field", + sprite_number: 14, + ), + transform: ( + translation: (128.0, 128.0, -0.1), + ), + ), + animation_set: ( + animations: [], + ), + collision_box: ( + upper_left_distance: [-2.0, 2.0], + lower_right_distance: [2.0, -2.0], + ), + movement_state: ( + velocity: [0.0, 0.0], + deaccel: 192.0, + reset: false, + ), + extras: BallData( + ball: Ball, + ), ), ), // Enemy @@ -335,37 +377,9 @@ Prefab( speed: 48.0, kick_strength: 256.0, push_strength: 96.0, + side: LowerSide, ), - robot: Robot, - ), - ), - ), - // Ball - PrefabEntity( - data: ( - sprite_scene: ( - render: ( - sheet: "field", - sprite_number: 14, - ), - transform: ( - translation: (128.0, 118.0, -0.1), - ), - ), - animation_set: ( - animations: [], - ), - collision_box: ( - upper_left_distance: [-2.0, 2.0], - lower_right_distance: [2.0, -2.0], - ), - movement_state: ( - velocity: [0.0, 0.0], - deaccel: 192.0, - reset: false, - ), - extras: BallData( - ball: Ball, + robot: Robot(logic_module: SillyRun), ), ), ), diff --git a/src/components/net.rs b/src/components/net.rs index 93ca543..bf0ef36 100644 --- a/src/components/net.rs +++ b/src/components/net.rs @@ -1,9 +1,17 @@ -extern crate amethyst; +use crate::utils::Side; +use amethyst::{ + assets::PrefabData, + derive::PrefabData, + ecs::{Component, DenseVecStorage, Entity, WriteStorage}, + Error, +}; +use serde::{Deserialize, Serialize}; -use amethyst::ecs::{Component, DenseVecStorage}; - -#[derive(Debug)] -pub struct Net; +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PrefabData)] +#[prefab(Component)] +pub struct Net { + pub side: Side, +} impl Component for Net { type Storage = DenseVecStorage; diff --git a/src/components/player.rs b/src/components/player.rs index d6b7f8b..9cd5f18 100644 --- a/src/components/player.rs +++ b/src/components/player.rs @@ -1,3 +1,4 @@ +use crate::utils; use amethyst::{ assets::PrefabData, derive::PrefabData, @@ -11,13 +12,14 @@ pub enum ActionType { Kick, } -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PrefabData)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PrefabData)] #[prefab(Component)] pub struct Player { pub speed: f32, pub action: Option, pub kick_strength: f32, pub push_strength: f32, + pub side: utils::Side, } impl Player { diff --git a/src/components/robot.rs b/src/components/robot.rs index da8fb77..9cf7129 100644 --- a/src/components/robot.rs +++ b/src/components/robot.rs @@ -6,9 +6,23 @@ use amethyst::{ }; use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PrefabData)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum LogicModule { + SillyRun, +} +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PrefabData)] #[prefab(Component)] -pub struct Robot; +pub struct Robot { + pub logic_module: LogicModule, +} + +impl Default for Robot { + fn default() -> Self { + Robot { + logic_module: LogicModule::SillyRun, + } + } +} impl Component for Robot { type Storage = DenseVecStorage; diff --git a/src/fbsim.rs b/src/fbsim.rs index c12f6d9..9a55a00 100644 --- a/src/fbsim.rs +++ b/src/fbsim.rs @@ -1,16 +1,17 @@ use crate::{components, config}; use amethyst::{ animation::AnimationSetPrefab, - assets::{PrefabData, PrefabLoader, ProgressCounter, RonFormat}, + assets::{AssetStorage, Loader, PrefabData, PrefabLoader, ProgressCounter, RonFormat}, core::transform::Transform, derive::PrefabData, - ecs::Entity, + ecs::{Entity, Read, ReadExpect}, error::Error, prelude::*, renderer::{ sprite::{prefab::SpriteScenePrefab, SpriteRender}, Camera, }, + ui, }; use serde::{Deserialize, Serialize}; @@ -18,8 +19,6 @@ use serde::{Deserialize, Serialize}; #[derive(Eq, PartialOrd, PartialEq, Hash, Debug, Copy, Clone, Deserialize, Serialize)] pub enum AnimationId { PlayerRun, - PlayerJump, - PlayerCelebrate, PlayerStand, } @@ -34,9 +33,13 @@ pub enum FieldSceneExtras { robot: Option, }, BallData { - /// Ball info. + /// Ball component. ball: components::Ball, }, + NetData { + /// Net component. + net: components::Net, + }, StaticData, } @@ -62,6 +65,42 @@ fn initialize_field(world: &mut World, progress_counter: &mut ProgressCounter) { world.create_entity().with(field_prefab).build(); } +fn initialize_score(world: &mut World, progress_counter: &mut ProgressCounter) { + // If we can't load the font just let it crash. + let font = world.exec( + |(loader, asset_storage): ( + ReadExpect<'_, Loader>, + Read<'_, AssetStorage>, + )| { + loader.load( + "fonts/slkscr.ttf", + ui::TtfFormat, + progress_counter, + &asset_storage, + ) + }, + ); + let text = ui::UiText::new( + font, + "0 - 0".to_string(), + [0.0, 0.0, 0.0, 1.0], + 25.0, + ui::LineMode::Single, + ui::Anchor::BottomLeft, + ); + let ui_transform = ui::UiTransform::new( + String::from("scoreboard"), // id + ui::Anchor::TopLeft, // anchor + ui::Anchor::TopLeft, // pivot + 20.0, // x + 0.0, // y + 0.4, // z + 100.0, // width + 30.0, // height + ); + world.create_entity().with(text).with(ui_transform).build(); +} + fn initialize_camera(world: &mut World) { let mut transform = Transform::default(); transform.set_translation_xyz(config::SCREEN_WIDTH / 2.0, config::SCREEN_HEIGHT / 2.0, 1.0); @@ -94,5 +133,6 @@ impl SimpleState for FieldState { let world = data.world; initialize_field(world, &mut self.progress_counter); initialize_camera(world); + initialize_score(world, &mut self.progress_counter); } } diff --git a/src/main.rs b/src/main.rs index 731058c..187b929 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ mod fbsim; use crate::fbsim::{AnimationId, FieldSceneData, FieldState}; - use amethyst::{ animation::AnimationBundle, assets::PrefabLoaderSystemDesc, @@ -13,12 +12,14 @@ use amethyst::{ types::DefaultBackend, RenderingBundle, SpriteRender, }, + ui::{RenderUi, UiBundle}, utils::application_root_dir, }; mod components; mod config; mod rectangle; +mod resources; mod systems; mod utils; @@ -42,18 +43,20 @@ fn main() -> amethyst::Result<()> { "sprite_animation_control", "sprite_sampler_interpolation", ))? + .with_bundle(input_bundle)? .with_bundle(TransformBundle::new())? + .with_bundle(UiBundle::::new())? .with_bundle( RenderingBundle::::new() // Plugin for easy rendering of windows. .with_plugin( RenderToWindow::from_config_path(display_config_path)? - .with_clear([0.0, 0.7, 0.0, 1.0]), + .with_clear([0.0, 0.6, 0.0, 1.0]), ) // RenderFlat2D plugin is used to render entities with a `SpriteRender` component. - .with_plugin(RenderFlat2D::default()), + .with_plugin(RenderFlat2D::default()) + .with_plugin(RenderUi::default()), )? - .with_bundle(input_bundle)? .with(systems::AnimatePlayer, "animate_player", &[]) .with( systems::Collisions, @@ -62,7 +65,13 @@ fn main() -> amethyst::Result<()> { ) .with(systems::MoveObjects, "move_objects", &["collisions"]) .with(systems::InputMovement, "input_movement", &["input_system"]) - .with(systems::InputActions, "input_actions", &["input_system"]); + .with(systems::InputActions, "input_actions", &["input_system"]) + .with( + systems::ai::SillyRun, + "ai_logic", + &["input_actions", "move_objects"], + ) + .with(systems::GoalResetter, "goal_resetter", &[]); let assets_dir = app_root.join("assets"); let mut game = Application::new(assets_dir, FieldState::new(), game_data)?; game.run(); diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..c9c921e --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1,3 @@ +pub use self::score::Score; + +mod score; diff --git a/src/resources/score.rs b/src/resources/score.rs new file mode 100644 index 0000000..40928c0 --- /dev/null +++ b/src/resources/score.rs @@ -0,0 +1,15 @@ +pub struct Score { + pub team1: u32, + pub team2: u32, + pub in_goal_last_frame: bool, +} + +impl Default for Score { + fn default() -> Self { + Score { + team1: 0, + team2: 0, + in_goal_last_frame: false, + } + } +} diff --git a/src/systems/ai/mod.rs b/src/systems/ai/mod.rs new file mode 100644 index 0000000..3515945 --- /dev/null +++ b/src/systems/ai/mod.rs @@ -0,0 +1,3 @@ +pub use self::silly::SillyRun; + +pub mod silly; diff --git a/src/systems/ai/silly.rs b/src/systems/ai/silly.rs new file mode 100644 index 0000000..d86de8d --- /dev/null +++ b/src/systems/ai/silly.rs @@ -0,0 +1,50 @@ +use crate::{components, components::robot::LogicModule, utils}; +use amethyst::{ + core::{math, Transform}, + derive::SystemDesc, + ecs::{Join, ReadStorage, System, SystemData, WriteStorage}, +}; + +#[derive(SystemDesc)] +pub struct SillyRun; + +impl<'s> System<'s> for SillyRun { + type SystemData = ( + WriteStorage<'s, components::Player>, + ReadStorage<'s, components::Ball>, + ReadStorage<'s, components::Robot>, + ReadStorage<'s, components::Human>, + ReadStorage<'s, components::Net>, + WriteStorage<'s, components::MovementState>, + ReadStorage<'s, Transform>, + ); + + fn run( + &mut self, + (mut players, balls, robots, _humans, nets, mut movement_states, transforms): Self::SystemData, + ) { + for (player, movement_state, player_transform, robot) in + (&mut players, &mut movement_states, &transforms, &robots).join() + { + if robot.logic_module == LogicModule::SillyRun { + let mut ball_position = math::Vector2::::new(0.0, 0.0); + let mut velocity = math::Vector2::::new(0.0, 0.0); + for (_, ball_transform) in (&balls, &transforms).join() { + ball_position += ball_transform.translation().xy(); + } + for (net, net_transform) in (&nets, &transforms).join() { + if net.side == utils::Side::LowerSide { + let net_position = net_transform.translation().xy(); + let player_position = player_transform.translation().xy(); + velocity = ((ball_position + net_position) / 2.0) - player_position; + } + } + if velocity.norm() > 1.0 { + movement_state.velocity = (velocity / velocity.norm()) * player.speed; + } else { + movement_state.velocity *= 0.0; + } + } + } + } +} diff --git a/src/systems/animate_player.rs b/src/systems/animate_player.rs index 9521060..e5b3121 100644 --- a/src/systems/animate_player.rs +++ b/src/systems/animate_player.rs @@ -4,11 +4,32 @@ use amethyst::{ animation::{ get_animation_set, AnimationCommand, AnimationControlSet, AnimationSet, EndControl, }, + core::Transform, derive::SystemDesc, ecs::{Entities, Join, ReadStorage, System, SystemData, WriteStorage}, renderer::SpriteRender, }; +fn rotate_player(movement_x: f32, movement_y: f32, transform: &mut Transform) { + let has_movement = movement_x.abs() + movement_y.abs() > 0.0; + if has_movement { + let rotation = if movement_x.abs() >= movement_y.abs() { + if movement_x >= 0.0 { + std::f32::consts::PI / 2.0 + } else { + 3.0 * std::f32::consts::PI / 2.0 + } + } else { + if movement_y >= 0.0 { + std::f32::consts::PI + } else { + 0.0 + } + }; + transform.set_rotation_2d(rotation); + } +} + #[derive(SystemDesc)] pub struct AnimatePlayer; @@ -18,15 +39,22 @@ impl<'s> System<'s> for AnimatePlayer { ReadStorage<'s, Player>, ReadStorage<'s, AnimationSet>, ReadStorage<'s, MovementState>, + WriteStorage<'s, Transform>, WriteStorage<'s, AnimationControlSet>, ); fn run( &mut self, - (entities, players, animation_sets, movement_states, mut control_sets): Self::SystemData, + (entities, players, animation_sets, movement_states,mut transforms, mut control_sets): Self::SystemData, ) { - for (entity, _player, movement_state, animation_set) in - (&entities, &players, &movement_states, &animation_sets).join() + for (entity, _player, movement_state, animation_set, transform) in ( + &entities, + &players, + &movement_states, + &animation_sets, + &mut transforms, + ) + .join() { // Creates a new AnimationControlSet for the entity let control_set = get_animation_set(&mut control_sets, entity).unwrap(); @@ -41,6 +69,7 @@ impl<'s> System<'s> for AnimatePlayer { AnimationCommand::Start, ); } else { + rotate_player(velocity.x, velocity.y, transform); control_set.abort(AnimationId::PlayerStand); control_set.add_animation( AnimationId::PlayerRun, diff --git a/src/systems/collision.rs b/src/systems/collision.rs index 14e41b0..618c8eb 100644 --- a/src/systems/collision.rs +++ b/src/systems/collision.rs @@ -1,12 +1,13 @@ use crate::{ - components::{collision_box, Ball, CollisionBox, MovementState, Player}, - config::*, - rectangle::Rectangle, + components::{collision_box, Ball, CollisionBox, MovementState, Net, Player}, + resources::Score, + utils::Side, }; use amethyst::{ core::Transform, derive::SystemDesc, - ecs::{Entities, Join, ReadStorage, System, SystemData, WriteStorage}, + ecs::{Join, ReadStorage, System, SystemData, Write, WriteStorage}, + ui::{UiFinder, UiText}, }; #[derive(SystemDesc)] @@ -14,77 +15,39 @@ pub struct Collisions; impl<'s> System<'s> for Collisions { type SystemData = ( - Entities<'s>, WriteStorage<'s, MovementState>, ReadStorage<'s, Ball>, ReadStorage<'s, Player>, + ReadStorage<'s, Net>, ReadStorage<'s, CollisionBox>, ReadStorage<'s, Transform>, + WriteStorage<'s, UiText>, + UiFinder<'s>, + Write<'s, Score>, ); fn run( &mut self, - (entities, mut movement_states, balls, players, collision_boxes, transforms): Self::SystemData, + ( + mut movement_states, + balls, + players, + nets, + collision_boxes, + transforms, + mut ui_texts, + ui_finder, + mut score, + ): Self::SystemData, ) { // TODO: some semblance of efficiency for all this. - // Handle collision that apply to all entities with a CollisionBox. - for (entity, movement_state, collision, transform) in ( - &entities, - &mut movement_states, - &collision_boxes, - &transforms, - ) - .join() - { - // First handle collisions with the end of the screen. - let upper_left = collision.upper_left_distance; - let lower_right = collision.lower_right_distance; - let pos = transform.translation().xy(); - let vel = movement_state.velocity; - let off_left = pos.x <= -upper_left.x && vel.x <= 0.0; - let off_right = pos.x >= SCREEN_WIDTH - lower_right.x && vel.x >= 0.0; - if off_left || off_right { - movement_state.velocity.x *= -1.0; - } - let off_bottom = pos.y <= -lower_right.y && vel.y <= 0.0; - let off_top = pos.y >= SCREEN_HEIGHT - upper_left.y && vel.y >= 0.0; - if off_bottom || off_top { - movement_state.velocity.y *= -1.0; - } - // Now let's handle any two pairs of collisions. - for (other_entity, other_collision, other_transform) in - (&entities, &collision_boxes, &transforms).join() - { - if entity.id() != other_entity.id() - && collision_box::are_colliding( - collision, - transform, - other_collision, - other_transform, - ) - { - // Ignore the case where we have ball vs. player - match (balls.get(entity), players.get(other_entity)) { - (Some(_), Some(_)) => continue, - _ => { - let box_center = collision.rectangle(transform).center(); - let other_box_center = - other_collision.rectangle(other_transform).center(); - let center_difference = box_center - other_box_center; - movement_state.velocity = center_difference / center_difference.norm() - * movement_state.velocity.norm(); - } - } - } - } - } // These handling collisions with balls, specifically. for (movement_state, _ball, ball_collision, ball_transform) in (&mut movement_states, &balls, &collision_boxes, &transforms).join() { - // Now handle collisions with players (i.e. kicks) - for (_player_entity, player, player_collision, player_transform) in - (&entities, &players, &collision_boxes, &transforms).join() + // Handle collisions with players (i.e. kicks) + for (player, player_collision, player_transform) in + (&players, &collision_boxes, &transforms).join() { if collision_box::are_colliding( ball_collision, @@ -98,6 +61,31 @@ impl<'s> System<'s> for Collisions { center_difference / center_difference.norm() * player.strength(); } } + let mut goal_collision = false; + // Handle collisions with goalines. + for (net, net_collision, net_transform) in (&nets, &collision_boxes, &transforms).join() + { + if collision_box::are_colliding( + ball_collision, + ball_transform, + net_collision, + net_transform, + ) { + goal_collision = true; + if !score.in_goal_last_frame { + let scoreboard = { + let scoreboard_entity = ui_finder.find("scoreboard").unwrap(); + ui_texts.get_mut(scoreboard_entity).unwrap() + }; + match net.side { + Side::UpperSide => score.team2 += 1, + Side::LowerSide => score.team1 += 1, + } + scoreboard.text = format!("{} - {}", score.team1, score.team2); + } + } + } + score.in_goal_last_frame = goal_collision; } } } diff --git a/src/systems/goal_resetter.rs b/src/systems/goal_resetter.rs new file mode 100644 index 0000000..c7f6ea2 --- /dev/null +++ b/src/systems/goal_resetter.rs @@ -0,0 +1,77 @@ +use crate::{ + components::{Ball, MovementState, Player}, + config::*, + resources::Score, + utils::Side, +}; +use amethyst::{ + core::Transform, + derive::SystemDesc, + ecs::{Join, Read, ReadStorage, System, SystemData, WriteStorage}, +}; + +#[derive(SystemDesc)] +pub struct GoalResetter; + +const MAXIMUM_TEAM_SIZE: usize = 5; +const RESET_PLAYER_POSITIONS: [[f32; 2]; MAXIMUM_TEAM_SIZE] = [ + [SCREEN_WIDTH / 2.0, 4.0 * SCREEN_HEIGHT / 9.0], + [SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 16.0], + [SCREEN_WIDTH / 4.0, SCREEN_HEIGHT / 3.0], + [3.0 * SCREEN_WIDTH / 4.0, SCREEN_HEIGHT / 3.0], + [SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 4.0], +]; + +impl<'s> System<'s> for GoalResetter { + type SystemData = ( + WriteStorage<'s, MovementState>, + WriteStorage<'s, Transform>, + ReadStorage<'s, Player>, + ReadStorage<'s, Ball>, + Read<'s, Score>, + ); + + fn run( + &mut self, + (mut movement_states, mut transforms, players, balls, score): Self::SystemData, + ) { + if !score.in_goal_last_frame { + return (); + } + let mut team_sizes = std::collections::BTreeMap::::new(); + for (movement_state, transform, player) in + (&mut movement_states, &mut transforms, &players).join() + { + let mut default_position = match team_sizes.get(&player.side) { + Some(v) => { + if *v < MAXIMUM_TEAM_SIZE { + RESET_PLAYER_POSITIONS[*v] + } else { + [0.0, 0.0] + } + } + None => RESET_PLAYER_POSITIONS[0], + }; + team_sizes.insert(player.side, team_sizes.get(&player.side).unwrap_or(&0) + 1); + if player.side == Side::UpperSide { + default_position[1] = SCREEN_HEIGHT - default_position[1]; + } + transform.set_translation_xyz( + default_position[0], + default_position[1], + transform.translation().z, + ); + movement_state.velocity *= 0.0; + } + for (_ball, movement_state, transform) in + (&balls, &mut movement_states, &mut transforms).join() + { + movement_state.velocity *= 0.0; + transform.set_translation_xyz( + SCREEN_WIDTH / 2.0, + SCREEN_HEIGHT / 2.0, + transform.translation().z, + ); + } + } +} diff --git a/src/systems/input_actions.rs b/src/systems/input_actions.rs index 875b83f..70bc8e9 100644 --- a/src/systems/input_actions.rs +++ b/src/systems/input_actions.rs @@ -1,7 +1,7 @@ -use crate::components::{player, Player}; +use crate::components; use amethyst::{ derive::SystemDesc, - ecs::{Join, Read, System, SystemData, WriteStorage}, + ecs::{Join, Read, ReadStorage, System, SystemData, WriteStorage}, input::{InputHandler, StringBindings}, }; @@ -10,19 +10,18 @@ pub struct InputActions; impl<'s> System<'s> for InputActions { type SystemData = ( - WriteStorage<'s, Player>, + WriteStorage<'s, components::Player>, + ReadStorage<'s, components::Human>, Read<'s, InputHandler>, ); - fn run(&mut self, (mut players, input): Self::SystemData) { + fn run(&mut self, (mut players, humans, input): Self::SystemData) { + let mut action = None; if let Some(true) = input.action_is_down("kick") { - for player in (&mut players).join() { - player.action = Some(player::ActionType::Kick); - } - } else { - for player in (&mut players).join() { - player.action = None; - } + action = Some(components::player::ActionType::Kick); + } + for (player, _) in (&mut players, &humans).join() { + player.action = action; } } } diff --git a/src/systems/input_movement.rs b/src/systems/input_movement.rs index cb44856..38c0669 100644 --- a/src/systems/input_movement.rs +++ b/src/systems/input_movement.rs @@ -3,7 +3,6 @@ use crate::{ utils, }; use amethyst::{ - core::Transform, derive::SystemDesc, ecs::{Join, Read, ReadStorage, System, SystemData, WriteStorage}, input::{InputHandler, StringBindings}, @@ -26,42 +25,16 @@ fn movement_multiplier(raw_movement_x: f32, raw_movement_y: f32, speed: f32) -> } } -fn rotate_player(movement_x: f32, movement_y: f32, transform: &mut Transform) { - let has_movement = movement_x.abs() + movement_y.abs() > 0.0; - if has_movement { - let rotation = if movement_x.abs() >= movement_y.abs() { - if movement_x >= 0.0 { - std::f32::consts::PI / 2.0 - } else { - 3.0 * std::f32::consts::PI / 2.0 - } - } else { - if movement_y >= 0.0 { - std::f32::consts::PI - } else { - 0.0 - } - }; - transform.set_rotation_2d(rotation); - } -} - impl<'s> System<'s> for InputMovement { type SystemData = ( ReadStorage<'s, Player>, ReadStorage<'s, Human>, WriteStorage<'s, MovementState>, - WriteStorage<'s, Transform>, Read<'s, InputHandler>, ); - fn run( - &mut self, - (players, humans, mut movement_states, mut transforms, input): Self::SystemData, - ) { - for (player, _human, movement_state, transform) in - (&players, &humans, &mut movement_states, &mut transforms).join() - { + fn run(&mut self, (players, humans, mut movement_states, input): Self::SystemData) { + for (player, _human, movement_state) in (&players, &humans, &mut movement_states).join() { let (raw_movement_x, raw_movement_y) = utils::input_movement(&input); let speed = player.speed; let move_multiplier = movement_multiplier(raw_movement_x, raw_movement_y, speed); @@ -69,7 +42,6 @@ impl<'s> System<'s> for InputMovement { raw_movement_x * move_multiplier, raw_movement_y * move_multiplier, ); - rotate_player(movement_x, movement_y, transform); movement_state.velocity.x += movement_x; movement_state.velocity.y += movement_y; } diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 34fb1e8..0af206a 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,11 +1,14 @@ pub use self::animate_player::AnimatePlayer; pub use self::collision::Collisions; +pub use self::goal_resetter::GoalResetter; pub use self::input_actions::InputActions; pub use self::input_movement::InputMovement; pub use self::move_objects::MoveObjects; -mod animate_player; -mod collision; -mod input_actions; -mod input_movement; -mod move_objects; +pub mod ai; +pub mod animate_player; +pub mod collision; +pub mod goal_resetter; +pub mod input_actions; +pub mod input_movement; +pub mod move_objects; diff --git a/src/systems/move_objects.rs b/src/systems/move_objects.rs index 7693220..33245e0 100644 --- a/src/systems/move_objects.rs +++ b/src/systems/move_objects.rs @@ -1,8 +1,12 @@ -use crate::components::MovementState; +use crate::{ + components::{collision_box, Ball, CollisionBox, MovementState, Net, Player}, + config::*, + rectangle::Rectangle, +}; use amethyst::{ core::{math, timing::Time, Transform}, derive::SystemDesc, - ecs::{Join, Read, System, SystemData, WriteStorage}, + ecs::{Entities, Join, Read, ReadStorage, System, SystemData, WriteStorage}, }; #[derive(SystemDesc)] @@ -10,13 +14,79 @@ pub struct MoveObjects; impl<'s> System<'s> for MoveObjects { type SystemData = ( + Entities<'s>, WriteStorage<'s, MovementState>, + ReadStorage<'s, CollisionBox>, WriteStorage<'s, Transform>, + ReadStorage<'s, Player>, + ReadStorage<'s, Ball>, + ReadStorage<'s, Net>, Read<'s, Time>, ); - fn run(&mut self, (mut movement_states, mut transforms, time): Self::SystemData) { + fn run( + &mut self, + ( + entities, + mut movement_states, + collision_boxes, + mut transforms, + players, + balls, + nets, + time, + ): Self::SystemData, + ) { let delta_seconds = time.delta_seconds(); + for (entity, movement_state, collision, transform) in ( + &entities, + &mut movement_states, + &collision_boxes, + &transforms, + ) + .join() + { + // Handle collision that apply to all entities with a CollisionBox. + // First handle collisions with the end of the screen. + let upper_left = collision.upper_left_distance; + let lower_right = collision.lower_right_distance; + let pos = transform.translation().xy() + movement_state.velocity * delta_seconds; + let vel = movement_state.velocity; + let off_left = pos.x <= -upper_left.x && vel.x <= 0.0; + let off_right = pos.x >= SCREEN_WIDTH - lower_right.x && vel.x >= 0.0; + if off_left || off_right { + movement_state.velocity.x *= -1.0; + } + let off_bottom = pos.y <= -lower_right.y && vel.y <= 0.0; + let off_top = pos.y >= SCREEN_HEIGHT - upper_left.y && vel.y >= 0.0; + if off_bottom || off_top { + movement_state.velocity.y *= -1.0; + } + // Now let's handle any two pairs of collisions. + for (other_entity, other_collision, other_transform) in + (&entities, &collision_boxes, &transforms).join() + { + if entity.id() != other_entity.id() + && collision_box::are_colliding( + collision, + transform, + other_collision, + other_transform, + ) + { + // Ignore the case where we have ball vs. player or ball vs. net. + if balls.get(entity).is_none() + || (players.get(other_entity).is_none() && nets.get(other_entity).is_none()) + { + let box_center = collision.rectangle(transform).center(); + let other_box_center = other_collision.rectangle(other_transform).center(); + let center_difference = box_center - other_box_center; + movement_state.velocity = center_difference / center_difference.norm() + * movement_state.velocity.norm(); + } + } + } + } for (movement_state, transform) in (&mut movement_states, &mut transforms).join() { let movement_norm = movement_state.velocity.norm(); if movement_norm > 0.0 { diff --git a/src/utils.rs b/src/utils.rs index 8c684ab..624bdd8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ use amethyst::input::{InputHandler, StringBindings}; +use serde::{Deserialize, Serialize}; pub fn input_movement(input: &InputHandler) -> (f32, f32) { let raw_movement_y = match input.axis_value("player_vertical") { @@ -11,3 +12,9 @@ pub fn input_movement(input: &InputHandler) -> (f32, f32) { }; (raw_movement_x, raw_movement_y) } + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord)] +pub enum Side { + UpperSide, + LowerSide, +}