diff --git a/Cargo.toml b/Cargo.toml index 484276b..2150cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["Gnarwhal "] edition = "2018" +[dependencies] +image = "0.23.7" + [dependencies.serde] version = "1" features = ["derive"] diff --git a/resources/levels/level0.png b/resources/levels/level0.png new file mode 100644 index 0000000..974f94c Binary files /dev/null and b/resources/levels/level0.png differ diff --git a/resources/sprites.png b/resources/sprites.png index 0ff23db..8785591 100644 Binary files a/resources/sprites.png and b/resources/sprites.png differ diff --git a/resources/sprites.ron b/resources/sprites.ron index 2446c6e..98d8540 100644 --- a/resources/sprites.ron +++ b/resources/sprites.ron @@ -1,12 +1,36 @@ List(( - texture_width: 16, - texture_height: 16, + texture_width: 54, + texture_height: 36, sprites: [ ( - x: 0, - y: 0, + x: 1, + y: 1, width: 16, height: 16, - ) + ), + ( + x: 19, + y: 1, + width: 16, + height: 16, + ), + ( + x: 19, + y: 19, + width: 16, + height: 16, + ), + ( + x: 37, + y: 1, + width: 16, + height: 16, + ), + ( + x: 37, + y: 19, + width: 16, + height: 16, + ), ], )) \ No newline at end of file diff --git a/src/components/mod.rs b/src/components/mod.rs index cbb88f8..5afc383 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -27,10 +27,12 @@ pub use self::{ physics::Gravity, physics::Dynamic, - physics::Static, player::Player, + + tile::Tile, }; pub mod physics; pub mod player; +pub mod tile; diff --git a/src/components/physics.rs b/src/components/physics.rs index 8e40312..2a085d7 100644 --- a/src/components/physics.rs +++ b/src/components/physics.rs @@ -24,15 +24,22 @@ * *******************************************************************************/ +use core::default::Default; use amethyst::{ core::math::Vector2, - ecs::prelude::{Component, DenseVecStorage} + ecs::prelude::{Component, DenseVecStorage, NullStorage} }; pub struct Gravity; impl Component for Gravity { - type Storage = DenseVecStorage; + type Storage = NullStorage; +} + +impl Default for Gravity { + fn default() -> Self { + Gravity + } } pub struct Dynamic { @@ -54,9 +61,3 @@ impl Default for Dynamic { impl Component for Dynamic { type Storage = DenseVecStorage; } - -pub struct Static; - -impl Component for Static { - type Storage = DenseVecStorage; -} diff --git a/src/components/tile.rs b/src/components/tile.rs new file mode 100644 index 0000000..0f5aff1 --- /dev/null +++ b/src/components/tile.rs @@ -0,0 +1,47 @@ +/******************************************************************************* + * + * Copyright (c) 2020 Gnarwhal + * + * ----------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files(the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + *******************************************************************************/ + +use core::default::Default; +use amethyst::{ + ecs::prelude::{Component, DenseVecStorage}, +}; + +pub enum Tile { + Background, + Ground, + Start, + End, +} + +impl Default for Tile { + fn default() -> Self { + Tile::Background + } +} + +impl Component for Tile { + type Storage = DenseVecStorage; +} diff --git a/src/main.rs b/src/main.rs index c34038e..6876164 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,6 @@ fn main() -> amethyst::Result<()> { let binding_path = resources_dir.join("bindings.ron"); let events_loop = EventsLoop::new(); - let monitor = MonitorIdent::from_primary(&events_loop); let mut display_config = DisplayConfig::load(display_config_path)?; @@ -76,8 +75,9 @@ fn main() -> amethyst::Result<()> { .with_plugin(RenderFlat2D::default()) )? .with(systems::ForceSystem, "force_system", &[]) - .with(systems::PlayerSystem, "player_system", &["force_system", "input_system"]) - .with(systems::CollisionSystem, "physics_system", &["player_system"]); + .with(systems::PlayerMovementSystem, "player_movement_system", &["force_system", "input_system"]) + .with(systems::CollisionSystem, "collision_system", &["player_movement_system"]) + .with(systems::CameraFollowSystem, "player_post_collision_system", &["collision_system"]); let mut game = Application::new(resources_dir, states::LevelState, game_data)?; game.run(); diff --git a/src/states/level.rs b/src/states/level.rs index 69cfa61..1317dd5 100644 --- a/src/states/level.rs +++ b/src/states/level.rs @@ -24,21 +24,127 @@ * *******************************************************************************/ +use image::Rgba; use amethyst::{ assets::{AssetStorage, Loader, Handle}, core::transform::Transform, prelude::*, + ecs::Entity, renderer::{Camera, ImageFormat, SpriteRender, SpriteSheet, SpriteSheetFormat, Texture} }; use crate::components::Dynamic; use crate::components::Gravity; use crate::components::Player; +use crate::components::Tile; pub const CAMERA_WIDTH: f32 = 384.0; pub const CAMERA_HEIGHT: f32 = 216.0; pub const BLOCK_SIZE: f32 = 16.0; +const BACKGROUND: Rgba = Rgba::([255, 255, 255, 255]); +const GROUND: Rgba = Rgba::([0, 0, 0, 255]); +const START: Rgba = Rgba::([0, 148, 255, 255]); +const END: Rgba = Rgba::([0, 216, 68, 255]); + +pub struct Level { + pub entities: Vec, + pub width: usize, + pub height: usize, + pub left: f32, + pub bottom: f32, + pub right: f32, + pub top: f32, +} + +impl Default for Level { + fn default() -> Self { + Level{ + entities: vec![], + width: 0, + height: 0, + left: 0.0, + bottom: 0.0, + right: 0.0, + top: 0.0, + } + } +} + +fn initialize_level(world: &mut World, sprite_sheet_handle: Handle) { + let background_sprite = SpriteRender { + sprite_sheet: sprite_sheet_handle.clone(), + sprite_number: 1, + }; + let ground_sprite = SpriteRender { + sprite_sheet: sprite_sheet_handle.clone(), + sprite_number: 2, + }; + let start_sprite = SpriteRender { + sprite_sheet: sprite_sheet_handle.clone(), + sprite_number: 4, + }; + let end_sprite = SpriteRender { + sprite_sheet: sprite_sheet_handle, + sprite_number: 3, + }; + + let level_image = image::open("resources/levels/level0.png").unwrap().into_rgba(); + let width = level_image.width() as usize; + let height = level_image.height() as usize; + let center_x = BLOCK_SIZE * width as f32 / 2.0; + let center_y = BLOCK_SIZE * height as f32 / 2.0; + let mut tile_map = Vec::::with_capacity(level_image.width() as usize * level_image.height() as usize); + for (i, pixel) in level_image.pixels().enumerate() { + let mut transform = Transform::default(); + transform.set_translation_xyz(BLOCK_SIZE * ((i % width) as f32 + 0.5) - center_x, BLOCK_SIZE * -((i / width) as f32 + 0.5) + center_y, -1.0); + match *pixel { + BACKGROUND => { + tile_map.push(world + .create_entity() + .with(Tile::Background) + .with(transform) + .build()); + }, + GROUND => { + tile_map.push(world + .create_entity() + .with(ground_sprite.clone()) + .with(Tile::Ground) + .with(transform) + .build()); + }, + START => { + tile_map.push(world + .create_entity() + .with(start_sprite.clone()) + .with(Tile::Start) + .with(transform) + .build()); + }, + END => { + tile_map.push(world + .create_entity() + .with(end_sprite.clone()) + .with(Tile::End) + .with(transform) + .build()); + }, + _ => { panic!("Invalid level bitmap tile color!"); } + } + } + let level = Level { + entities: tile_map, + width, + height, + left: -(BLOCK_SIZE * width as f32 / 2.0), + bottom: -(BLOCK_SIZE * height as f32 / 2.0), + right: (BLOCK_SIZE * width as f32 / 2.0), + top: (BLOCK_SIZE * height as f32 / 2.0), + }; + world.insert(level); +} + fn initialize_camera(world: &mut World) { let mut transform = Transform::default(); transform.set_translation_xyz(0.0, 0.0, 1.0); @@ -57,13 +163,12 @@ fn initialize_player(world: &mut World, sprite_sheet_handle: Handle }; let mut transform = Transform::default(); - transform.set_translation_xyz(0.0, 48.0, 0.0); + transform.set_translation_xyz(BLOCK_SIZE * -11.0, BLOCK_SIZE * -12.5, 0.0); world .create_entity() .with(sprite_render) .with(Player::default()) - .with(Transform::default()) .with(Dynamic::default()) .with(Gravity) .with(transform) @@ -101,6 +206,9 @@ impl SimpleState for LevelState { let sprite_sheet_handle = load_sprite_sheet(world); initialize_camera(world); - initialize_player(world, sprite_sheet_handle); + initialize_player(world, sprite_sheet_handle.clone()); + + world.register::(); + initialize_level(world, sprite_sheet_handle); } } \ No newline at end of file diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 13a209f..bdb1525 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -28,8 +28,9 @@ pub use self::{ physics::ForceSystem, physics::CollisionSystem, - player::PlayerSystem, player::PlayerBindings, + player::PlayerMovementSystem, + player::CameraFollowSystem, }; pub mod physics; diff --git a/src/systems/physics.rs b/src/systems/physics.rs index 027af58..6cdc263 100644 --- a/src/systems/physics.rs +++ b/src/systems/physics.rs @@ -25,22 +25,26 @@ *******************************************************************************/ use amethyst::{ - core::timing::Time, + core::{ + timing::Time, + math::Vector3, + }, core::{Transform}, derive::SystemDesc, ecs::{Join, Read, ReadStorage, System, SystemData, WriteStorage}, }; use crate::components::Dynamic; -use crate::components::Static; use crate::components::Gravity; +use crate::components::Tile; use crate::systems::player::FULL_HOP_TIME; use crate::systems::player::FULL_HOP_HEIGHT; use crate::systems::player::MAX_GROUND_SPEED; use crate::systems::player::MAX_AERIAL_SPEED; -use crate::states::level::CAMERA_HEIGHT; +use crate::states::level::Level; +use crate::states::level::BLOCK_SIZE; -pub const FRICTION: f32 = MAX_GROUND_SPEED / 0.25; -pub const AIR_RESISTANCE: f32 = MAX_AERIAL_SPEED / 1.0; +pub const FRICTION: f32 = MAX_GROUND_SPEED / 0.1; +pub const AIR_RESISTANCE: f32 = MAX_AERIAL_SPEED / 0.5; pub const GRAVITY: f32 = -2.0 * FULL_HOP_HEIGHT / (FULL_HOP_TIME * FULL_HOP_TIME); @@ -78,26 +82,82 @@ impl<'s> System<'s> for ForceSystem { #[derive(SystemDesc)] pub struct CollisionSystem; +fn attempt_collision(object: &mut Vector3, level: &Read, tiles: &ReadStorage) -> u32 { + let left = (((object.x - level.left ) / BLOCK_SIZE - 0.5).floor() as usize).min(level.width - 1).max(0); + let bottom = (((object.y - level.bottom) / BLOCK_SIZE - 0.5).floor() as usize).min(level.height - 1).max(0); + let right = (((object.x - level.left ) / BLOCK_SIZE - 0.5).ceil() as usize).min(level.width - 1).max(0); + let top = (((object.y - level.bottom) / BLOCK_SIZE - 0.5).ceil() as usize).min(level.height - 1).max(0); + let mut closest = Option::<(usize, usize)>::None; + let mut distance = (BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE * BLOCK_SIZE); + for i in left..=right { + for j in bottom..=top { + match (&mut closest, tiles.get(level.entities[(level.height - j - 1) * level.width + i]).unwrap()) { + (None, Tile::Ground) | (None, Tile::Start) | (None, Tile::End) => { + closest = Some((i, j)); + let dist_x = (object.x - level.left ) - (i as f32 + 0.5) * BLOCK_SIZE; + let dist_y = (object.y - level.bottom) - (j as f32 + 0.5) * BLOCK_SIZE; + distance = (dist_x, dist_y, (dist_x * dist_x + dist_y * dist_y).sqrt()); + }, + (Some(_), Tile::Ground) | (Some(_), Tile::Start) | (Some(_), Tile::End) => { + let dist_x = (object.x - level.left ) - (i as f32 + 0.5) * BLOCK_SIZE; + let dist_y = (object.y - level.bottom) - (j as f32 + 0.5) * BLOCK_SIZE; + let current_distance = (dist_x * dist_x + dist_y * dist_y).sqrt(); + if current_distance < distance.2 { + closest = Some((i, j)); + distance = (dist_x, dist_y, current_distance); + } + } + (_, Tile::Background) => {}, + } + } + } + if let Some((x, y)) = closest { + if distance.0.abs() > distance.1.abs() { + object.x += (BLOCK_SIZE - distance.0.abs()) * distance.0.signum(); + (2 - distance.0.signum() as i32) as u32 + } else { + object.y += (BLOCK_SIZE - distance.1.abs()) * distance.1.signum(); + (3 - distance.1.signum() as i32) as u32 + } + } else { + 0 + } +} + impl<'s> System<'s> for CollisionSystem { type SystemData = ( WriteStorage<'s, Transform>, WriteStorage<'s, Dynamic>, - ReadStorage<'s, Static>, + ReadStorage<'s, Tile>, + Read<'s, Level>, Read<'s, Time>, ); - fn run(&mut self, (mut transforms, mut dynamics, statics, delta_time): Self::SystemData) { + fn run(&mut self, (mut transforms, mut dynamics, tiles, level, delta_time): Self::SystemData) { for (transform, dynamic) in (&mut transforms, &mut dynamics).join() { let translation = transform.translation_mut(); translation.x += dynamic.velocity.x * delta_time.delta_seconds(); translation.y += dynamic.velocity.y * delta_time.delta_seconds(); - const FLOOR: f32 = -CAMERA_HEIGHT / 2.0 + 8.0; - if translation.y < FLOOR { - translation.y = FLOOR; - dynamic.velocity.y = 0.0; - dynamic.grounded = true; - } else { - dynamic.grounded = false; + dynamic.grounded = false; + let mut result = 1; + while result != 0 { + result = attempt_collision(translation, &level, &tiles); + match result { + 1 => { + dynamic.velocity.x = 0.0; + }, + 2 => { + dynamic.velocity.y = 0.0; + dynamic.grounded = true; + }, + 3 => { + dynamic.velocity.x = 0.0; + }, + 4 => { + dynamic.velocity.y = 0.0; + }, + _ => {} + } } } } diff --git a/src/systems/player.rs b/src/systems/player.rs index 9edd3fb..fdfe5f4 100644 --- a/src/systems/player.rs +++ b/src/systems/player.rs @@ -27,15 +27,21 @@ use std::fmt::{self, Display}; use amethyst::{ - core::timing::Time, + core::{ + timing::Time, + Transform, + math::Vector3, + }, derive::SystemDesc, - ecs::{Join, Read, System, SystemData, WriteStorage}, + ecs::{Join, Read, ReadStorage, System, SystemData, WriteStorage}, input::{InputHandler, BindingTypes}, + renderer::{Camera}, }; use serde::{Serialize, Deserialize}; use crate::components::Dynamic; use crate::components::Player; use crate::systems::physics::GRAVITY; +use crate::states::level::{Level, CAMERA_WIDTH, CAMERA_HEIGHT}; use crate::states::level::BLOCK_SIZE; #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] @@ -69,23 +75,23 @@ impl BindingTypes for PlayerBindings { type Action = ActionBindings; } -pub const FULL_HOP_TIME: f32 = 0.35; -pub const SHORT_HOP_HEIGHT: f32 = 1.5 * BLOCK_SIZE; -pub const FULL_HOP_HEIGHT: f32 = 3.25 * BLOCK_SIZE; -pub const AERIAL_HOP_HEIGHT: f32 = 2.25 * BLOCK_SIZE; +pub const FULL_HOP_TIME: f32 = 0.3; +pub const SHORT_HOP_HEIGHT: f32 = 1.25 * BLOCK_SIZE; +pub const FULL_HOP_HEIGHT: f32 = 2.25 * BLOCK_SIZE; +pub const AERIAL_HOP_HEIGHT: f32 = 1.25 * BLOCK_SIZE; const JUMP_COUNT: usize = 2; -pub const MAX_GROUND_SPEED: f32 = 10.0 * BLOCK_SIZE; +pub const MAX_GROUND_SPEED: f32 = 6.0 * BLOCK_SIZE; const GROUND_ACCELERATION: f32 = (MAX_GROUND_SPEED * 2.0) / 0.1; -pub const MAX_AERIAL_SPEED: f32 = 12.0 * BLOCK_SIZE; +pub const MAX_AERIAL_SPEED: f32 = 8.0 * BLOCK_SIZE; const AERIAL_ACCELERATION: f32 = (MAX_AERIAL_SPEED * 2.0) / 0.5; const AERIAL_JUMP_HORZ_BOOST: f32 = AERIAL_ACCELERATION * 0.3; #[derive(SystemDesc)] -pub struct PlayerSystem; +pub struct PlayerMovementSystem; -impl <'s> System<'s> for PlayerSystem { +impl <'s> System<'s> for PlayerMovementSystem { type SystemData = ( WriteStorage<'s, Dynamic>, WriteStorage<'s, Player>, @@ -160,3 +166,38 @@ impl <'s> System<'s> for PlayerSystem { } } } + +#[derive(SystemDesc)] +pub struct CameraFollowSystem; + +impl<'s> System<'s> for CameraFollowSystem { + type SystemData = ( + WriteStorage<'s, Player>, + ReadStorage<'s, Camera>, + WriteStorage<'s, Transform>, + Read<'s, Level>, + ); + + fn run(&mut self, (mut players, cameras, mut transforms, level): Self::SystemData) { + let mut player_transform = Vector3::new(0.0, 0.0, 0.0); + for (current_player_transform, _) in (&transforms, &mut players).join() { + player_transform = current_player_transform.translation().clone(); + } + for (camera_transform, _) in (&mut transforms, &cameras).join() { + const HALF_WIDTH: f32 = CAMERA_WIDTH / 2.0; + const HALF_HEIGHT: f32 = CAMERA_HEIGHT / 2.0; + if player_transform.x - HALF_WIDTH < level.left { + player_transform.x = level.left + HALF_WIDTH; + } else if player_transform.x + HALF_WIDTH > level.right { + player_transform.x = level.right - HALF_WIDTH; + } + + if player_transform.y - HALF_HEIGHT < level.bottom { + player_transform.y = level.bottom + HALF_HEIGHT; + } else if player_transform.y + HALF_HEIGHT > level.top { + player_transform.y = level.top - HALF_HEIGHT; + } + camera_transform.set_translation_xyz(player_transform.x, player_transform.y, 1.0); + } + } +} \ No newline at end of file