diff --git a/Cargo.toml b/Cargo.toml index a3d3a2ab63e51..6416d422bc640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4198,6 +4198,17 @@ description = "Demonstrates specular tints and maps" category = "3D Rendering" wasm = true +[[example]] +name = "projectile_follow_moving" +path = "examples/usage/projectile_follow_moving.rs" +doc-scrape-examples = true + +[package.metadata.example.projectile_follow_moving] +name = "Projectile follow moving" +description = "A projectile is fired on mousclick following a moving target." +category = "usage" +wasm = true + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/examples/README.md b/examples/README.md index 060683f96d891..6d8f0d0f5c40b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -68,6 +68,7 @@ git checkout v0.4.0 - [Transforms](#transforms) - [UI (User Interface)](#ui-user-interface) - [Window](#window) + - [usage](#usage) - [Tests](#tests) - [Platform-Specific Examples](#platform-specific-examples) @@ -588,6 +589,12 @@ Example | Description [Window Resizing](../examples/window/window_resizing.rs) | Demonstrates resizing and responding to resizing a window [Window Settings](../examples/window/window_settings.rs) | Demonstrates customizing default window settings +## usage + +Example | Description +--- | --- +[Projectile follow moving](../examples/usage/projectile_follow_moving.rs) | A projectile is fired on mousclick following a moving target. + # Tests Example | Description diff --git a/examples/usage/projectile_follow_moving.rs b/examples/usage/projectile_follow_moving.rs new file mode 100644 index 0000000000000..9373d7d5a6f39 --- /dev/null +++ b/examples/usage/projectile_follow_moving.rs @@ -0,0 +1,202 @@ +//! Shows how to create moving and spining objects on mouse position. +//! +//! Left click on with the mouse will spawn a projectile that follows the moving target. + +use bevy::{input::common_conditions::input_just_pressed, prelude::*}; + +const MIN_DISTANCE: f32 = 10.0; + +/// Represents a movable entity with a spawn location, movement speed, and a maximum allowed distance from its spawn point. +/// +/// This component is later used by `Follower` projectiles to select and follow a target. +#[derive(Component, Debug)] +struct Movable { + spawn: Vec3, + max_distance: f32, + speed: f32, +} + +/// Utility constructor for creating a `Movable` with default speed and max distance values. +impl Movable { + fn new(spawn: Vec3) -> Self { + Movable { + spawn, + max_distance: 300.0, + speed: 70.0, + } + } +} + +/// Marks a projectile that follows a target entity, using a specified speed. +/// +/// - `target`: An `Entity` identifier used to reference the target in queries. +/// - `speed`: The speed at which the projectile moves toward the target. +#[derive(Component)] +struct Follower { + speed: f32, + target: Entity, +} + +impl Follower { + /// Creates a new `Follower` with a default speed and the given target entity. + fn new(target: Entity) -> Self { + Follower { + speed: 110.0, + target, + } + } +} + +/// A resource that holds the mesh and material used for rendering 2D projectiles. +#[derive(Resource)] +struct WorldAssets { + mesh: Handle, + material: Handle, +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, move_target) + .add_systems( + Update, + spawn_projectile.run_if(input_just_pressed(MouseButton::Left)), + ) + .add_systems( + Update, + move_projectile.run_if(any_with_component::), + ) + .run(); +} + +// Startup system to setup the scene and spawn all relevant entities. +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Create mesh handle and material for projectile. + let projectile_asset = WorldAssets { + mesh: meshes.add(Triangle2d::new( + Vec2::Y * 5.0, + Vec2::new(-5.0, -5.0), + Vec2::new(5.0, -5.0), + )), + material: materials.add(Color::from(bevy::color::palettes::basic::RED)), + }; + + // Add handler of triabgle and it's material as Resource. + commands.insert_resource(projectile_asset); + + // Add a shape as Movable. + let entity_spawn = Vec3::ZERO; + commands.spawn(( + Mesh2d(meshes.add(RegularPolygon::new(30.0, 8))), + MeshMaterial2d(materials.add(Color::WHITE)), + Transform::from_translation(entity_spawn), + Movable::new(entity_spawn), + )); + + // Spawn a camera looking at the entities to show what's happening in this example. + commands.spawn((Camera2d, Transform::from_xyz(0.0, 0.0, 0.0))); + + // this creates a Text comment and the Node is need as a Ui description to spawn it in the top left corner + commands.spawn(( + Text::new("Mouseclick: to fire projectile from mouse position towards moving target."), + Node { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }, + )); +} + +/// Spawns a projectile at the current mouse cursor position and assigns the only `Movable` entity as its target. +/// +/// The function performs the following steps: +/// 1. Calculates the cursor position relative to the world by using the current window and camera viewport. +/// 2. Retrieves the first and only entity with the `Movable` component to be used as the projectile's target. +/// 3. Spawns the projectile at the computed world position using the `WorldAsset` resource created during setup. +fn spawn_projectile( + mut commands: Commands, + world_assets: Res, + targets: Single<(Entity, &mut Movable)>, + camera_query: Single<(&Camera, &GlobalTransform)>, + window: Query<&Window>, +) -> Result<(), BevyError> { + let (camera, camera_transform) = *camera_query; + let window = window.single()?; + + let Some(cursor_position) = window.cursor_position() else { + return Ok(()); + }; + + // Calculate a world position based on the cursor's position. + let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else { + return Ok(()); + }; + + let entity_spawn = Vec3::new(world_pos.x, world_pos.y, 0.0); + + let target_entity = targets.0; + commands.spawn(( + Mesh2d(world_assets.mesh.clone()), + MeshMaterial2d(world_assets.material.clone()), + Transform::from_translation(entity_spawn), + Follower::new(target_entity), + )); + + Ok(()) +} + +/// This system will move all Movable entities with a Transform +fn move_target(mut targets: Query<(&mut Transform, &mut Movable)>, timer: Res