我一直在尝试和bevy一起制作一款FPS游戏。第二天我遇到了一个问题,那就是如果我将玩家的手臂和武器模型附加到相机上,那么它是不可见的。看来 bevy 的渲染器通过算法将其隐藏起来,尽管这种情况不应该发生。我该如何解决这个问题?
我尝试使用
CheckVisibility
集中的系统强制它可见,但这似乎也不起作用。我不确定我在这里犯了什么错。
我将 fps 模型从相机上拆下来进行测试,果然,它渲染得很好,直到我靠近它,它实际上应该在的位置。然后,它就消失了。 :-(
这是我的代码:
use crate::character_controller::{
CharacterControllerBundle, CharacterControllerPlugin, DirectionLooker,
};
use bevy::{
ecs::event::ManualEventReader,
input::mouse::MouseMotion,
prelude::*,
render::view::{check_visibility, VisibilitySystems::CheckVisibility},
window::{CursorGrabMode, PrimaryWindow},
};
use bevy_xpbd_3d::{math::Scalar, prelude::*};
/// Marker component representing a `Camera` attached to the player
#[derive(Component)]
pub struct FPSCam;
/// Marker component representing the player
#[derive(Component)]
pub struct Player;
/// Marker component representing the player's view model
#[derive(Component)]
pub struct PlayerViewModel;
pub struct PlayerPlugin;
impl Plugin for PlayerPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<InputState>()
.init_resource::<LookSettings>()
.add_plugins(CharacterControllerPlugin)
.add_systems(Startup, setup)
.add_systems(Update, (grab, look))
.add_systems(
PostUpdate,
make_visible.in_set(CheckVisibility).after(check_visibility),
);
}
}
#[derive(Resource, Default)]
struct InputState {
reader_motion: ManualEventReader<MouseMotion>,
}
#[derive(Resource)]
pub struct LookSettings {
pub sensitivity: f32,
}
impl Default for LookSettings {
fn default() -> Self {
Self {
sensitivity: 0.00006,
}
}
}
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
// player
let player = commands
.spawn((
SpatialBundle {
transform: Transform::from_xyz(0.0, 1.5, 0.0),
..default()
},
Player,
DirectionLooker,
CharacterControllerBundle::new(Collider::capsule(1.0, 0.4))
.with_movement(30.0, 0.92, 7.0, (30.0 as Scalar).to_radians()),
Friction::ZERO.with_combine_rule(CoefficientCombine::Min),
Restitution::ZERO.with_combine_rule(CoefficientCombine::Min),
GravityScale(2.0),
))
.id();
let mut fps_model_transform = Transform::from_xyz(0.0, 0.7, 0.0);
fps_model_transform.rotate_y(180.0_f32.to_radians());
let _fps_model = commands
.spawn((
SceneBundle {
scene: assets.load("mp5.glb#Scene0"),
transform: fps_model_transform,
..default()
},
PlayerViewModel,
))
.id();
// camera
let camera = commands
.spawn((
Camera3dBundle {
projection: PerspectiveProjection {
fov: 80.0_f32.to_radians(),
near: 0.001,
..default()
}
.into(),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
},
FPSCam,
))
.id();
commands.entity(player).push_children(&[camera]);
}
fn make_visible(mut query: Query<&mut ViewVisibility, With<PlayerViewModel>>) {
for mut visibility in &mut query {
visibility.set();
}
}
fn grab(
mut windows: Query<&mut Window>,
keys: Res<Input<KeyCode>>,
mouse: Res<Input<MouseButton>>,
) {
let mut window = windows.single_mut();
if mouse.just_pressed(MouseButton::Right) {
window.cursor.visible = false;
window.cursor.grab_mode = CursorGrabMode::Locked;
} else if keys.just_pressed(KeyCode::Escape) {
window.cursor.visible = true;
window.cursor.grab_mode = CursorGrabMode::None;
}
}
fn look(
settings: Res<LookSettings>,
primary_window: Query<&Window, With<PrimaryWindow>>,
motion: Res<Events<MouseMotion>>,
mut state: ResMut<InputState>,
mut player_query: Query<(&mut Transform, With<Player>, Without<FPSCam>)>,
mut camera_query: Query<(&mut Transform, With<FPSCam>, Without<Player>)>,
) {
if let Ok(window) = primary_window.get_single() {
for ev in state.reader_motion.read(&motion) {
for (mut player_transform, _, _) in player_query.iter_mut() {
let mut yaw =
player_transform.rotation.to_euler(EulerRot::YXZ).0;
match window.cursor.grab_mode {
CursorGrabMode::None => (),
_ => {
// Using smallest of height or width ensures equal
// vertical and horizontal sensitivity
let window_scale = window.height().min(window.width());
yaw -=
(settings.sensitivity * ev.delta.x * window_scale)
.to_radians();
}
}
player_transform.rotation = Quat::from_axis_angle(Vec3::Y, yaw);
}
for (mut camera_transform, _, _) in camera_query.iter_mut() {
let mut pitch =
camera_transform.rotation.to_euler(EulerRot::YXZ).1;
match window.cursor.grab_mode {
CursorGrabMode::None => (),
_ => {
// Using smallest of height or width ensures equal
// vertical and horizontal sensitivity
let window_scale = window.height().min(window.width());
pitch -=
(settings.sensitivity * ev.delta.y * window_scale)
.to_radians();
}
}
camera_transform.rotation =
Quat::from_axis_angle(Vec3::X, pitch.clamp(-1.54, 1.54));
}
}
} else {
warn!("Primary window not found!");
}
}
发生这种情况的原因是 Bevy 的自动视锥体剔除。通过将
NoFrustumCulling
组件添加到 Mesh
可以轻松修复它。但是,由于我没有使用普通的 Mesh
而是使用 glTF Scene
,因此我使用 bevy-scene-hook
将 NoFrustumCulling
组件应用到具有 Handle<Mesh>
的任何内容。现在可以了。
let fps_model = commands
.spawn(HookedSceneBundle {
scene: SceneBundle {
scene: assets.load("mp5.glb#Scene0"),
transform: fps_model_transform,
..default()
},
hook: SceneHook::new(|entity, commands| {
if entity.get::<Handle<Mesh>>().is_some() {
commands.insert(NoFrustumCulling);
}
}),
})
.id();
当然,请确保从
bevy_scene_hook
箱中导入适当的类型。另外,您还需要添加 HookPlugin
。