如何在Bevy中根据起止点调整线段的Transform朝向
解决Bevy中立方体线段表示的朝向问题
问题根源
你当前的实现逻辑存在两处核心错误:
- 错误通过线段中心的坐标值选择
looking_at的上方向,完全不符合立方体线段的方向规律 - Bevy的
Cylinder默认沿Y轴延伸,而looking_at是让实体Z轴朝向目标,两者轴向不匹配导致旋转逻辑完全错位
修正方案
正确做法是直接基于线段的方向向量,将圆柱的默认Y轴对齐到线段方向,步骤如下:
- 计算线段的方向向量并归一化
- 使用
Quat::from_rotation_arc生成从默认Y轴到目标方向的旋转四元数 - 将圆柱平移到线段中心
- 设置圆柱高度为线段的实际长度
修正后的完整代码
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, )); } }
关键说明
- 轴向对齐:Bevy的
Cylinder默认沿Y轴拉伸,因此需要将Y轴旋转到线段方向,而非使用控制Z轴朝向的looking_at - 旋转计算:
Quat::from_rotation_arc是Bevy中计算两个向量间旋转的标准方法,能生成无歧义的四元数 - 边生成优化:替换全量顶点连线逻辑,只生成立方体的12条边,符合线框立方体的预期效果
- 性能提示:当前
update函数每次销毁重建所有物体,实际项目中建议仅初始化生成一次,或用组件更新替代销毁重建
内容的提问来源于stack exchange,提问作者recouer
相关产品推荐
相关产品推荐

