Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add collision to your ldtk map #220

Open
Abubakar1122331 opened this issue Aug 15, 2023 · 1 comment
Open

How to add collision to your ldtk map #220

Abubakar1122331 opened this issue Aug 15, 2023 · 1 comment

Comments

@Abubakar1122331
Copy link

Abubakar1122331 commented Aug 15, 2023

I want to add collision for any these ldtk.
Tell me how do you separate tiles for collision and add colliders to it.

Here's my main.rs:

use bevy::{
   prelude::*,
   window::{PrimaryWindow, WindowTheme},
};
use bevy_ecs_ldtk::prelude::*;
use bevy_inspector_egui::prelude::*;
use bevy_inspector_egui::quick::WorldInspectorPlugin;
use std::collections::{HashMap, HashSet};

use components::Player;

mod components;
mod systems;

const PLAYER_SPEED: f32 = 500.0;
const PLAYER_SIZE: f32 = 24.0;
const GRAVITY: f32 = 1.0;
const FALL_SPEED: f32 = 10.0;

fn main() {
   App::new()
       .insert_resource(ClearColor(Color::rgb(0., 0., 0.)))
       .add_plugins(
           DefaultPlugins
               .set(ImagePlugin::default_nearest())
               .set(WindowPlugin {
                   primary_window: Some(Window {
                       title: "Bevy Fish Fight".into(),
                       resolution: (800.0, 800.0).into(),
                       resizable: false,
                       fit_canvas_to_parent: true,
                       window_theme: Some(WindowTheme::Dark),
                       ..Default::default()
                   }),
                   ..Default::default()
               }),
       )
       .add_plugins(WorldInspectorPlugin::new())
       .add_plugins(LdtkPlugin)
       .add_systems(Startup, setup)
       .insert_resource(LevelSelection::Index(1))
       .add_systems(Startup, systems::spawn_player)
       .add_systems(Update, systems::animate_sprite)
       .add_systems(Update, systems::camera_with_player_movement)
       .add_systems(Update, systems::player_movement)
       .add_systems(Update, systems::player_jump)
       // .add_systems(Update, systems::confine_player_movement)
       .run();
}

fn setup(
   mut commands: Commands,
   asset_server: Res<AssetServer>,
   windows: Query<&Window, With<PrimaryWindow>>,
) {
   commands.spawn(Camera2dBundle::default());
   let window = windows.get_single().unwrap();

   let ldtk_img = commands.spawn((
       LdtkWorldBundle {
           ldtk_handle: asset_server.load("Typical_2D_platformer_example.ldtk"),
           transform: Transform {
               scale: Vec3 {
                   x: 4.5,
                   y: 4.5,
                   z: 1.0,
               },
               translation: Vec3 {
                   x: -window.width() / 2.0,
                   y: -window.height() / 2.0,
                   z: 0.0,
               },
               ..Default::default()
           },
           ..Default::default()
       },
       Name::new("Level"),
   ));
}

This is my systems.rs:

use crate::components::*;
use bevy::prelude::*;
use bevy_ecs_ldtk::prelude::*;

use std::collections::{HashMap, HashSet};

use bevy_rapier2d::prelude::*;

use crate::*;

pub fn spawn_player(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
    let texture_handle = asset_server.load("gabe-idle-run.png");

    let texture_atlas =
        TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
    let texture_atlas_handle = texture_atlases.add(texture_atlas);

    let animation_indices = AnimationIndices { first: 1, last: 6 };
    // Use only the subset of sprites in the sheet that make up the running animation
    commands.spawn((
        SpriteSheetBundle {
            texture_atlas: texture_atlas_handle,
            sprite: TextureAtlasSprite::new(3),
            transform: Transform {
                translation: Vec3 {
                    x: 0.0,
                    y: 0.0,
                    z: 4.5,
                },
                scale: Vec3::new(3.0, 3.0, 0.0),
                ..default()
            },
            ..default()
        },
        Name::new("Player"),
        Player,
        animation_indices,
        AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
    ));
}

pub fn player_movement(
    keyboard_input: Res<Input<KeyCode>>,
    mut player_query: Query<&mut Transform, With<Player>>,
    time: Res<Time>,
) {
    if let Ok(mut transform) = player_query.get_single_mut() {
        let mut direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::Left) || keyboard_input.pressed(KeyCode::A) {
            direction += Vec3::new(-1.0, 0.0, 0.0);
            transform.scale = Vec3::new(-3.0, 3.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Right) || keyboard_input.pressed(KeyCode::D) {
            direction += Vec3::new(1.0, 0.0, 0.0);
            transform.scale = Vec3::new(3.0, 3.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Up) || keyboard_input.pressed(KeyCode::W) {
            direction += Vec3::new(0.0, 1.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Down) || keyboard_input.pressed(KeyCode::S) {
            direction += Vec3::new(0.0, -1.0, 0.0);
        }

        if direction.length() > 0.0 {
            direction = direction.normalize();
        }

        transform.translation += direction * PLAYER_SPEED * time.delta_seconds();
    }
}

pub fn camera_with_player_movement(
    keyboard_input: Res<Input<KeyCode>>,
    mut camera_query: Query<&mut Transform, With<Camera>>,
    time: Res<Time>,
) {
    if let Ok(mut transform) = camera_query.get_single_mut() {
        let mut direction = Vec3::ZERO;

        if keyboard_input.pressed(KeyCode::Left) || keyboard_input.pressed(KeyCode::A) {
            direction += Vec3::new(-1.0, 0.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Right) || keyboard_input.pressed(KeyCode::D) {
            direction += Vec3::new(1.0, 0.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Up) || keyboard_input.pressed(KeyCode::W) {
            direction += Vec3::new(0.0, 1.0, 0.0);
        }
        if keyboard_input.pressed(KeyCode::Down) || keyboard_input.pressed(KeyCode::S) {
            direction += Vec3::new(0.0, -1.0, 0.0);
        }

        if direction.length() > 0.0 {
            direction = direction.normalize();
        }

        transform.translation += direction * PLAYER_SPEED * time.delta_seconds();
    }
}

pub fn confine_player_movement(
    mut player_query: Query<&mut Transform, With<Player>>,
    window_query: Query<&Window, With<PrimaryWindow>>,
) {
    if let Ok(mut player_transform) = player_query.get_single_mut() {
        let window = window_query.get_single().unwrap();

        let half_player_size = PLAYER_SIZE;
        let x_min = -window.width() / 2.0 + half_player_size - 2.8;
        let x_max = window.width() / 2.0 - half_player_size;

        let mut translation = player_transform.translation;

        // Bound the player X position
        if translation.x < x_min {
            translation.x = x_min
        } else if translation.x > x_max {
            translation.x = x_max
        }

        player_transform.translation = translation;
    }
}

pub fn animate_sprite(
    keyboard_input: Res<Input<KeyCode>>,
    time: Res<Time>,
    mut query: Query<(
        &AnimationIndices,
        &mut AnimationTimer,
        &mut TextureAtlasSprite,
    )>,
) {
    for (indices, mut timer, mut sprite) in &mut query {
        timer.tick(time.delta());
        if keyboard_input.pressed(KeyCode::Right)
            || keyboard_input.pressed(KeyCode::D)
            || keyboard_input.pressed(KeyCode::Left)
            || keyboard_input.pressed(KeyCode::A)
        {
            if timer.just_finished() {
                sprite.index = if sprite.index == indices.last {
                    indices.first
                } else {
                    sprite.index + 1
                };
            }
        } else {
            sprite.index = 0
        }
    }
}

pub fn player_jump(
    mut commands: Commands,
    keyboard_input: Res<Input<KeyCode>>,
    mut player: Query<(Entity, &mut Transform, &mut Jump), With<Player>>,
    time: Res<Time>,
) {
    let Ok((player, mut transform, mut jump)) = player.get_single_mut() else {return;};
    let jump_power = (time.delta_seconds() * FALL_SPEED * 2.).min(jump.0);
    jump.0 -= jump_power;
    transform.translation.y += jump_power;
    if jump.0 == 0. {
        commands.entity(player).remove::<Jump>();
    }
}

components.rs:

use bevy::prelude::*;
use bevy_inspector_egui::prelude::*;
use bevy_inspector_egui::quick::WorldInspectorPlugin;

#[derive(Component, InspectorOptions)]
pub struct Player;

#[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Component)]
pub struct Wall;

#[derive(Component, Deref, DerefMut)]
pub struct AnimationTimer(pub Timer);

#[derive(Component, InspectorOptions)]
pub struct AnimationIndices {
    pub first: usize,
    pub last: usize,
}

#[derive(Component)]
pub struct Jump(pub f32);
@Abubakar1122331 Abubakar1122331 changed the title Adding collision to your ldtk map How to add collision to your ldtk map Aug 15, 2023
@Trouv
Copy link
Owner

Trouv commented Aug 15, 2023

There's a system in the platformer example that does this sort of thing, but fyi the platformer example uses rapier for physics/collision. Naively, you could spawn a collider for every Wall tile, and even do so through LdtkIntCell registration, but this leaves you with a lot of colliders in a level which slows things down a lot.

Instead, the platformer example uses the Wall component as a marker, then has a separate system that "merges" wall tiles into new, fewer, larger rectangle collider entities: https://github.com/Trouv/bevy_ecs_ldtk/blob/v0.8.0/examples/platformer/systems.rs#L78

Though, it doesn't look like you're spawning these components in any way. Be sure to do so either through the registering LdtkEntity/LdtkIntCell bundles to the app, or by fleshing them out in a system that queries for Added<EntityInstance> or Added<IntGridCell>, or some combination of the two: https://docs.rs/bevy_ecs_ldtk/latest/bevy_ecs_ldtk/#entity-and-intgrid-layers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants