已用唯一组件过滤,Bevy ECS查询仍冲突的原因及解决
我尝试在Bevy系统函数中执行以下两个查询:
fn move_player( mut player_query: Query<(&mut Velocity, &mut Transform, &SpriteSize, &Player), With<PlayerId>>, wall_query: Query<(&Transform, &SpriteSize), With<Barrier>>, ) { for (mut player_velocity, mut player_tf, player_size, player) in player_query.iter_mut() { for (wall_tf, wall_size) in wall_query.iter() { } } }
生成玩家实体时添加了PlayerId组件,生成墙体实体时添加了Barrier组件,且PlayerId未添加至墙体实体,Barrier也未添加至玩家实体。运行后出现如下错误:
thread 'main' panicked at 'error[B0001]: Query<(&mut bevy_transform::components::transform::Transform, bevy_fantasy::SpriteSize), bevy_ecs::query::filter::With<bevy_fantasy::Barrier>> in system bevy_fantasy::player::move_player accesses component(s) bevy_transform::components::transform::Transform in a way that conflicts with a previous system parameter. Consider using
Without<T>to create disjoint Queries or merging conflicting Queries into aParamSet.
请问为何使用唯一组件过滤后,这两个查询仍会产生冲突?
这是因为Bevy的查询冲突检测是静态检查,它不会在系统运行前验证你的过滤条件(With<PlayerId>/With<Barrier>)是否真的让两个查询的实体集合完全不重叠。
冲突的核心在于:第一个查询以可变引用(&mut Transform)的方式访问Transform组件,第二个查询以不可变引用(&Transform)的方式访问同一份Transform组件。Bevy的ECS为了保证线程安全和数据一致性,默认不允许同一系统中同时存在对同一组件的可变和不可变访问——哪怕你知道过滤后实体不会重叠,静态检查阶段也不会做这个判断。
方法一:明确声明查询实体不重叠
给第二个查询添加Without<PlayerId>(或Without<Player>)过滤条件,直接告诉Bevy这两个查询的实体集合完全没有交集,静态检查就会通过:
fn move_player( mut player_query: Query<(&mut Velocity, &mut Transform, &SpriteSize, &Player), With<PlayerId>>, // 添加Without<PlayerId>明确排除玩家实体 wall_query: Query<(&Transform, &SpriteSize), (With<Barrier>, Without<PlayerId>)>, ) { // 原有逻辑不变 }
方法二:使用ParamSet包裹冲突查询
如果确实需要在同一系统中同时持有对同一组件的可变和不可变访问,可以用ParamSet把两个查询包裹起来。它允许你在同一系统中访问冲突的查询,但同一时间只能使用其中一个:
use bevy_ecs::system::ParamSet; fn move_player( mut params: ParamSet<( Query<(&mut Velocity, &mut Transform, &SpriteSize, &Player), With<PlayerId>>, Query<(&Transform, &SpriteSize), With<Barrier>>, )>, ) { // 先使用第一个查询(p0) for (mut player_velocity, mut player_tf, player_size, player) in params.p0().iter_mut() { // 处理玩家逻辑,此时不能访问p1 } // 再使用第二个查询(p1) for (wall_tf, wall_size) in params.p1().iter() { // 处理墙体逻辑,此时不能访问p0 } }
内容的提问来源于stack exchange,提问作者simplelenz

