You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Bevy中根据起止点调整线段的Transform朝向

解决Bevy中立方体线段表示的朝向问题

问题根源

你当前的实现逻辑存在两处核心错误:

  • 错误通过线段中心的坐标值选择looking_at的上方向,完全不符合立方体线段的方向规律
  • Bevy的Cylinder默认沿Y轴延伸,而looking_at是让实体Z轴朝向目标,两者轴向不匹配导致旋转逻辑完全错位

修正方案

正确做法是直接基于线段的方向向量,将圆柱的默认Y轴对齐到线段方向,步骤如下:

  1. 计算线段的方向向量并归一化
  2. 使用Quat::from_rotation_arc生成从默认Y轴到目标方向的旋转四元数
  3. 将圆柱平移到线段中心
  4. 设置圆柱高度为线段的实际长度

修正后的完整代码

use bevy::{
    input::mouse::{MouseMotion, MouseWheel},
    prelude::*,
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, (update, camera_movement_action))
        .run();
}

#[derive(Component)]
pub struct GameCamera;

#[derive(Component)]
pub struct Object;

fn setup(
    mut commands: Commands<'_, '_>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn(PointLightBundle {
        point_light: PointLight {
            shadows_enabled: true,
            intensity: 10_000_000.,
            range: 100.0,
            shadow_depth_bias: 0.2,
            ..default()
        },
        transform: Transform::from_xyz(8.0, 16.0, 8.0),
        ..default()
    });

    commands.spawn((
        Camera3dBundle {
            transform: Transform::from_xyz(0.0, 7., 14.0)
                .looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
            ..default()
        },
        GameCamera,
    ));
}

pub fn camera_movement_action(
    mut camera_transform_query: Query<&mut Transform, With<GameCamera>>,
    mouse_input: Res<ButtonInput<MouseButton>>,
    mut mouse_motion: EventReader<MouseMotion>,
    mut mouse_scroll: EventReader<MouseWheel>,
) {
    if let Ok(mut camera_transform) = camera_transform_query.get_single_mut() {
        if mouse_input.pressed(MouseButton::Right) {
            for motion in mouse_motion.read() {
                camera_transform
                    .rotate_around(Vec3::ZERO, Quat::from_rotation_z(motion.delta.y * -0.01));
                camera_transform
                    .rotate_around(Vec3::ZERO, Quat::from_rotation_y(motion.delta.x * -0.01));
                camera_transform.rotation =
                    camera_transform.looking_at(Vec3::ZERO, Vec3::Y).rotation;
            }
        }

        for scroll in mouse_scroll.read() {
            let forward = camera_transform.forward();
            camera_transform.translation += forward * scroll.y;
        }
    }
}

fn update(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    object_query: Query<Entity, With<Object>>,
) {
    // 清理旧物体
    for entity in object_query.iter() {
        commands.entity(entity).despawn_recursive();
    }

    // 立方体8个顶点
    let points = vec![
        Vec3::new(1., 1., 1.),
        Vec3::new(1., -1., 1.),
        Vec3::new(1., 1., -1.),
        Vec3::new(1., -1., -1.),
        Vec3::new(-1., 1., 1.),
        Vec3::new(-1., -1., 1.),
        Vec3::new(-1., 1., -1.),
        Vec3::new(-1., -1., -1.),
    ];

    // 生成顶点标记
    for point in points.iter() {
        commands.spawn((
            PbrBundle {
                mesh: meshes.add(Cuboid::new(0.1, 0.1, 0.1)),
                material: materials.add(StandardMaterial::default()),
                transform: Transform::from_translation(*point),
                ..default()
            },
            Object,
        ));
    }

    // 立方体的12条边(替换全量连线,避免生成对角线)
    let edges = [
        (0, 1), (0, 2), (0, 4),
        (1, 3), (1, 5),
        (2, 3), (2, 6),
        (3, 7),
        (4, 5), (4, 6),
        (5, 7),
        (6, 7),
    ];

    for &(start_i, finish_i) in &edges {
        let start_p = points[start_i];
        let finish_p = points[finish_i];

        // 计算线段中心
        let center = (start_p + finish_p) / 2.0;
        // 计算线段方向向量与长度
        let direction = finish_p - start_p;
        let length = direction.length();
        // 归一化方向向量
        let dir_normalized = direction.normalize();

        // 生成旋转:将默认Y轴对齐到线段方向
        let rotation = Quat::from_rotation_arc(Vec3::Y, dir_normalized);

        // 生成线段圆柱
        commands.spawn((
            PbrBundle {
                mesh: meshes.add(Cylinder::new(0.02, length)),
                material: materials.add(StandardMaterial::default()),
                transform: Transform::from_translation(center).with_rotation(rotation),
                ..default()
            },
            Object,
        ));
    }
}

关键说明

  1. 轴向对齐:Bevy的Cylinder默认沿Y轴拉伸,因此需要将Y轴旋转到线段方向,而非使用控制Z轴朝向的looking_at
  2. 旋转计算:Quat::from_rotation_arc是Bevy中计算两个向量间旋转的标准方法,能生成无歧义的四元数
  3. 边生成优化:替换全量顶点连线逻辑,只生成立方体的12条边,符合线框立方体的预期效果
  4. 性能提示:当前update函数每次销毁重建所有物体,实际项目中建议仅初始化生成一次,或用组件更新替代销毁重建

内容的提问来源于stack exchange,提问作者recouer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:02:08