Final product. Very incomplete but you can platform.

This commit is contained in:
Gnarwhal 2024-08-07 05:09:37 +00:00
parent 7f2694ab24
commit 1b14306b27
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
12 changed files with 332 additions and 45 deletions

View file

@ -4,6 +4,9 @@ version = "0.1.0"
authors = ["Gnarwhal <git.aspect893@passmail.net>"]
edition = "2018"
[dependencies]
image = "0.23.7"
[dependencies.serde]
version = "1"
features = ["derive"]

BIN
resources/levels/level0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 B

After

Width:  |  Height:  |  Size: 306 B

View file

@ -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,
),
],
))

View file

@ -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;

View file

@ -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<Self>;
type Storage = NullStorage<Self>;
}
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<Self>;
}
pub struct Static;
impl Component for Static {
type Storage = DenseVecStorage<Self>;
}

47
src/components/tile.rs Normal file
View file

@ -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<Self>;
}

View file

@ -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();

View file

@ -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<u8> = Rgba::<u8>([255, 255, 255, 255]);
const GROUND: Rgba<u8> = Rgba::<u8>([0, 0, 0, 255]);
const START: Rgba<u8> = Rgba::<u8>([0, 148, 255, 255]);
const END: Rgba<u8> = Rgba::<u8>([0, 216, 68, 255]);
pub struct Level {
pub entities: Vec<Entity>,
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<SpriteSheet>) {
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::<Entity>::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<SpriteSheet>
};
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::<Tile>();
initialize_level(world, sprite_sheet_handle);
}
}

View file

@ -28,8 +28,9 @@ pub use self::{
physics::ForceSystem,
physics::CollisionSystem,
player::PlayerSystem,
player::PlayerBindings,
player::PlayerMovementSystem,
player::CameraFollowSystem,
};
pub mod physics;

View file

@ -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<f32>, level: &Read<Level>, tiles: &ReadStorage<Tile>) -> 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.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;
} else {
dynamic.grounded = false;
},
3 => {
dynamic.velocity.x = 0.0;
},
4 => {
dynamic.velocity.y = 0.0;
},
_ => {}
}
}
}
}

View file

@ -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);
}
}
}