Giter VIP home page Giter VIP logo

seldom_map_nav's Introduction

seldom_map_nav

Crates.io MIT/Apache 2.0 Crates.io

seldom_map_nav is a Bevy plugin that does navmesh generation, pathfinding, and navigation for tilemaps. Navmesh generation is available without Bevy dependency. It is agnostic to the solution you use for your tilemaps, but doesn't require much glue. It is also agnostic to your position type, as long as it implements Position2 (ex. Transform). It's compatible with seldom_state with the state feature.

Features

  • Navmesh generation for finite, square tilemaps
  • Awareness of navigator physical size
  • Bevy plugin for pathfinding and navigation
  • Integration with seldom_state

Future Work

This crate is currently in maintenance mode, so I'm not currently adding new features.

  • Tiles that can be pathed over in certain situations, such as doors
  • Tiles that cannot be pathed over, but do not need clearance generated, such as holes

The generated paths are not always optimal, even with the greatest quality settings, but I do not plan to fix this myself. If possible, I may switch dependencies to improve this, though.

seldom_state Compatibility

If the state feature is enabled, it will trigger the DoneTrigger when it is done navigating (if it reaches the destination or cannot find a path). Also, see related example: cargo run --example state --features="state"

Usage

Add to your Cargo.toml

# Replace * with your desired version
[dependencies]
seldom_map_nav = "*"

To generate navmeshes without Bevy integration, disable the bevy feature and use Navmeshes::generate or seldom_map_nav::mesh::generate_navmesh. See the no_bevy.rs example.

To generate paths without using the built-in navigation, add the MapNavPlugin to your app, add the Navmeshes component to your tilemap (or some other entity), and add the Pathfind component to your navigating entity. To use the built-in navigation, also add the Nav component to your navigating entity. See the nav.rs example. If you are having trouble getting it to generate a path, enable the log feature, and it might tell you what's wrong.

If you need help, feel free to ping me on the Bevy Discord server (@Seldom)! If any of the docs need improvement, feel free to submit an issue or pr!

Compatibility

Bevy seldom_state seldom_map_nav
0.11 0.7 0.5
0.10 0.6 0.4
0.10 0.5 0.3
0.9 0.3 0.2
0.8 0.2 0.1

License

seldom_map_nav is dual-licensed under MIT and Apache 2.0 at your option.

Contributing

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

seldom_map_nav's People

Contributors

ciderslime avatar martinlindhe avatar seldom-se avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

seldom_map_nav's Issues

Integration with seldom_state: unexpected DoneTrigger behaviour.

How to reproduce:

add new example nav_state.rs, paste code below. run with cargo run --example nav_state --features="state"

Expected behaviour

piece of code

.trans::<GoToSelection>(DoneTrigger::Success, Idle)
            .on_enter::<Idle>(|_| { info!("IDLE!"); })

should print IDLE! after movement ends. receiving trigger from MapNavPlugin

Problem

IDLE! is printed just after the moving start.
image
P.S. Also I tried to run same example inside seldom_state crate with nessecary changes in Cargo.toml and in assets. IDLE! message not shown at all
image

Further digging

then I added some custom info! to nav.rs
image
image
and output was:
image
here we see strange Done Empty trigger just after the click, which causes undesired state change.
while moving, there is no triggers.
Done Finished Nav occurs the right moment, when the movement is done
Finally, there will be flood of Done Empty triggers, until we initiate next movement (dunno if it is right)

Code for nav_state.rs

// In this game, the player navigates to wherever you click

use bevy::{prelude::*, sprite::Anchor};
use rand::{thread_rng, Rng};
use seldom_map_nav::prelude::*;
use seldom_state::prelude::*;

#[derive(Clone, Reflect)]
struct Click;

impl OptionTrigger for Click {
    type Param<'w, 's> = (Res<'w, Input<MouseButton>>, Res<'w, CursorPos>);
    type Some = Vec2;

    fn trigger(&self, _: Entity, (mouse, cursor_position): Self::Param<'_, '_>) -> Option<Vec2> {
        mouse
            .just_pressed(MouseButton::Left)
            .then_some(())
            .and(**cursor_position)
    }
}

#[derive(Clone, Component, Reflect)]
#[component(storage = "SparseSet")]
struct Idle;

#[derive(Clone, Copy, Component, Reflect)]
#[component(storage = "SparseSet")]
struct GoToSelection {
    speed: f32,
    target: Vec2,
}

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, StateMachinePlugin, MapNavPlugin::<Transform>::default()))
        // This plugin is required for pathfinding and navigation
        // The type parameter is the position component that you use
        .init_resource::<CursorPos>()
        .add_systems(Startup, init)
        .add_systems(Update, (update_cursor_pos, move_player).chain())
        .run();
}

const MAP_SIZE: UVec2 = UVec2::new(24, 24);
const TILE_SIZE: Vec2 = Vec2::new(32., 32.);
// This is the radius of a square around the player that should not intersect with the terrain
const PLAYER_CLEARANCE: f32 = 8.;

fn init(mut commands: Commands, asset_server: Res<AssetServer>) {
    commands.spawn(Camera2dBundle {
        // Centering the camera
        transform: Transform::from_translation((MAP_SIZE.as_vec2() * TILE_SIZE / 2.).extend(999.9)),
        ..default()
    });

    let mut rng = thread_rng();
    // Randomly generate the tilemap
    let tilemap = [(); (MAP_SIZE.x * MAP_SIZE.y) as usize].map(|_| match rng.gen_bool(0.8) {
        true => Navability::Navable,
        false => Navability::Solid,
    });
    let navability = |pos: UVec2| tilemap[(pos.y * MAP_SIZE.x + pos.x) as usize];

    // Spawn images for the tiles
    let tile_image = asset_server.load("tile.png");
    let mut player_pos = default();
    for x in 0..MAP_SIZE.x {
        for y in 0..MAP_SIZE.y {
            let pos = UVec2::new(x, y);
            if let Navability::Navable = navability(pos) {
                let pos = UVec2::new(x, y).as_vec2() * TILE_SIZE;
                player_pos = pos;

                commands.spawn(SpriteBundle {
                    sprite: Sprite {
                        anchor: Anchor::BottomLeft,
                        ..default()
                    },
                    transform: Transform::from_translation(pos.extend(0.)),
                    texture: tile_image.clone(),
                    ..default()
                });
            }
        }
    }

    // Here's the important bit:

    // Spawn the tilemap with a `Navmeshes` component
    commands
        .spawn(Navmeshes::generate(MAP_SIZE, TILE_SIZE, navability, [PLAYER_CLEARANCE]).unwrap());

    // Spawn the player component. A position component is necessary. We will add `NavBundle`
    // later.
    commands.spawn((
        SpriteBundle {
            transform: Transform::from_translation((player_pos + TILE_SIZE / 2.).extend(1.)),
            texture: asset_server.load("player.png"),
            ..default()
        },
        StateMachine::default()
            // When the player clicks, go there
            .trans_builder(Click, |_: &AnyState, pos| {
                Some(GoToSelection {
                    speed: 200.,
                    target: pos,
                })
            })
            // `DoneTrigger` triggers when the `Done` component is added to the entity. When they're
            // done going to the selection, idle.
            .trans::<GoToSelection>(DoneTrigger::Success, Idle)
            .on_enter::<Idle>(|_| { info!("IDLE!"); })
            .on_enter::<GoToSelection>(|_| { info!("GOTO!"); })
            .set_trans_logging(true),
        Player,
        Idle
    ));
}

// Navigate the player to wherever you click
fn move_player(
    mut commands: Commands,
    players: Query<(Entity, &GoToSelection), Added<GoToSelection>>,
    navmesheses: Query<Entity, With<Navmeshes>>,
) {
    // Clicked somewhere on the screen!
    // Add `NavBundle` to start navigating to that position
    // If you want to write your own movement, but still want paths generated,
    // only insert `Pathfind`.
    for (entity, go_to_selection) in &players {
        commands.entity(entity).insert(NavBundle {
            pathfind: Pathfind::new(
                navmesheses.single(),
                PLAYER_CLEARANCE,
                None,
                PathTarget::Static(go_to_selection.target),
                NavQuery::Accuracy,
                NavPathMode::Accuracy,
            ),
            nav: Nav::new(go_to_selection.speed),
        });
    }
}

// The code after this comment is not related to `seldom_map_nav`

#[derive(Component)]
struct Player;

#[derive(Default, Deref, DerefMut, Resource)]
struct CursorPos(Option<Vec2>);

fn update_cursor_pos(
    cameras: Query<(&Camera, &GlobalTransform)>,
    windows: Query<&Window>,
    mut position: ResMut<CursorPos>,
) {
    let (camera, transform) = cameras.single();
    **position = windows
        .single()
        .cursor_position()
        .and_then(|cursor_pos| camera.viewport_to_world_2d(transform, cursor_pos));
}

Can`t use navigation with Navmeshes for isometric map

Thank you for the great plugin!
The problem is that Navmeshes::generate() returns Navmeshes bound to square topdown map.
I have no idea how to use it with isometric map.
If I could pass a Square-to-Iso translation function, as argument to Navmeshes::generate(), or just a map_type, that would help me a lot.
Pathfinding seems to work fine, but it receives squared-tile collisions info from NavMeshes
Any advice for workaround this problem will also be accepted.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.