1
1
//! A 2D platformer example with one-way platforms to demonstrate
2
- //! contact modification with `CollisionHooks` .
2
+ //! filtering collisions with systems in the `PostProcessCollisions` schedule .
3
3
//!
4
4
//! Move with arrow keys, jump with Space and descend through
5
5
//! platforms by pressing Space while holding the down arrow.
6
6
7
- #![ allow( clippy:: type_complexity) ]
8
-
9
7
use avian2d:: { math:: * , prelude:: * } ;
10
- use bevy:: {
11
- ecs:: system:: { lifetimeless:: Read , SystemParam } ,
12
- prelude:: * ,
13
- utils:: HashSet ,
14
- } ;
8
+ use bevy:: { prelude:: * , utils:: HashSet } ;
15
9
use examples_common_2d:: ExampleCommonPlugin ;
16
10
17
11
fn main ( ) {
18
12
App :: new ( )
19
13
. add_plugins ( (
20
14
DefaultPlugins ,
21
15
ExampleCommonPlugin ,
22
- PhysicsPlugins :: default ( )
23
- // Specify a units-per-meter scaling factor, 1 meter = 20 pixels.
24
- // The unit allows the engine to tune its parameters for the scale of the world, improving stability.
25
- . with_length_unit ( 20.0 )
26
- // Add our custom collision hooks.
27
- . with_collision_hooks :: < PlatformerCollisionHooks > ( ) ,
16
+ // Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
17
+ // The unit allows the engine to tune its parameters for the scale of the world, improving stability.
18
+ PhysicsPlugins :: default ( ) . with_length_unit ( 20.0 ) ,
28
19
) )
29
20
. insert_resource ( ClearColor ( Color :: srgb ( 0.05 , 0.05 , 0.1 ) ) )
30
21
. insert_resource ( Gravity ( Vector :: NEG_Y * 1000.0 ) )
31
22
. add_systems ( Startup , setup)
32
23
. add_systems ( Update , ( movement, pass_through_one_way_platform) )
24
+ . add_systems ( PostProcessCollisions , one_way_platform)
33
25
. run ( ) ;
34
26
}
35
27
@@ -42,10 +34,7 @@ struct MovementSpeed(Scalar);
42
34
#[ derive( Component ) ]
43
35
struct JumpImpulse ( Scalar ) ;
44
36
45
- // Enable contact modification for one-way platforms with the `ActiveCollisionHooks` component.
46
- // Here we use required components, but you could also add it manually.
47
37
#[ derive( Clone , Eq , PartialEq , Debug , Default , Component ) ]
48
- #[ require( ActiveCollisionHooks ( || ActiveCollisionHooks :: MODIFY_CONTACTS ) ) ]
49
38
pub struct OneWayPlatform ( HashSet < Entity > ) ;
50
39
51
40
/// A component to control how an actor interacts with a one-way platform.
@@ -183,70 +172,70 @@ fn pass_through_one_way_platform(
183
172
}
184
173
}
185
174
186
- // Define a custom `SystemParam` for our collision hooks.
187
- // It can have read-only access to queries, resources, and other system parameters.
188
- #[ derive( SystemParam ) ]
189
- struct PlatformerCollisionHooks < ' w , ' s > {
190
- one_way_platforms_query : Query < ' w , ' s , Read < OneWayPlatform > > ,
191
- // NOTE: This precludes a `OneWayPlatform` passing through a `OneWayPlatform`.
175
+ /// Allows entities to pass through [`OneWayPlatform`] entities.
176
+ ///
177
+ /// Passing through is achieved by removing the collisions between the [`OneWayPlatform`]
178
+ /// and the other entity if the entity should pass through.
179
+ /// If a [`PassThroughOneWayPlatform`] is present on the non-platform entity,
180
+ /// the value of the component dictates the pass-through behaviour.
181
+ ///
182
+ /// Entities known to be passing through each [`OneWayPlatform`] are stored in the
183
+ /// [`OneWayPlatform`]. If an entity is known to be passing through a [`OneWayPlatform`],
184
+ /// it is allowed to continue to do so, even if [`PassThroughOneWayPlatform`] has been
185
+ /// set to disallow passing through.
186
+ ///
187
+ /// > Note that this is a very simplistic implementation of one-way
188
+ /// > platforms to demonstrate filtering collisions via [`PostProcessCollisions`].
189
+ /// > You will probably want something more robust to implement one-way
190
+ /// > platforms properly, or may elect to use a sensor collider for your entities instead,
191
+ /// > which means you won't need to filter collisions at all.
192
+ ///
193
+ /// #### When an entity is known to already be passing through the [`OneWayPlatform`]
194
+ ///
195
+ /// Any time an entity begins passing through a [`OneWayPlatform`], it is added to the
196
+ /// [`OneWayPlatform`]'s set of currently active penetrations, and will be allowed to
197
+ /// continue to pass through the platform until it is no longer penetrating the platform.
198
+ ///
199
+ /// The entity is allowed to continue to pass through the platform as long as at least
200
+ /// one contact is penetrating.
201
+ ///
202
+ /// Once all of the contacts are no longer penetrating the [`OneWayPlatform`], or all contacts
203
+ /// have stopped, the entity is forgotten about and the logic falls through to the next part.
204
+ ///
205
+ /// #### When an entity is NOT known to be passing through the [`OneWayPlatform`]
206
+ ///
207
+ /// Depending on the setting of [`PassThroughOneWayPlatform`], the entity may be allowed to
208
+ /// pass through.
209
+ ///
210
+ /// If no [`PassThroughOneWayPlatform`] is present, [`PassThroughOneWayPlatform::ByNormal`] is used.
211
+ ///
212
+ /// [`PassThroughOneWayPlatform`] may be in one of three states:
213
+ /// 1. [`PassThroughOneWayPlatform::ByNormal`]
214
+ /// - This is the default state
215
+ /// - The entity may be allowed to pass through the [`OneWayPlatform`] depending on the contact normal
216
+ /// - If all contact normals are in line with the [`OneWayPlatform`]'s local-space up vector,
217
+ /// the entity is allowed to pass through
218
+ /// 2. [`PassThroughOneWayPlatform::Always`]
219
+ /// - The entity will always pass through the [`OneWayPlatform`], regardless of contact normal
220
+ /// - This is useful for allowing an entity to jump down through a platform
221
+ /// 3. [`PassThroughOneWayPlatform::Never`]
222
+ /// - The entity will never pass through the [`OneWayPlatform`], meaning the platform will act
223
+ /// as normal hard collision for this entity
224
+ ///
225
+ /// Even if an entity is changed to [`PassThroughOneWayPlatform::Never`], it will be allowed to pass
226
+ /// through a [`OneWayPlatform`] if it is already penetrating the platform. Once it exits the platform,
227
+ /// it will no longer be allowed to pass through.
228
+ fn one_way_platform (
229
+ mut one_way_platforms_query : Query < & mut OneWayPlatform > ,
192
230
other_colliders_query : Query <
193
- ' w ,
194
- ' s ,
195
- Option < Read < PassThroughOneWayPlatform > > ,
196
- ( With < Collider > , Without < OneWayPlatform > ) ,
231
+ Option < & PassThroughOneWayPlatform > ,
232
+ ( With < Collider > , Without < OneWayPlatform > ) , // NOTE: This precludes OneWayPlatform passing through a OneWayPlatform
197
233
> ,
198
- }
199
-
200
- // Implement the `CollisionHooks` trait for our custom system parameter.
201
- impl CollisionHooks for PlatformerCollisionHooks < ' _ , ' _ > {
202
- // Below is a description of the logic used for one-way platforms.
203
-
204
- /// Allows entities to pass through [`OneWayPlatform`] entities.
205
- ///
206
- /// Passing through is achieved by removing the collisions between the [`OneWayPlatform`]
207
- /// and the other entity if the entity should pass through.
208
- /// If a [`PassThroughOneWayPlatform`] is present on the non-platform entity,
209
- /// the value of the component dictates the pass-through behaviour.
210
- ///
211
- /// Entities known to be passing through each [`OneWayPlatform`] are stored in the
212
- /// [`OneWayPlatform`]. If an entity is known to be passing through a [`OneWayPlatform`],
213
- /// it is allowed to continue to do so, even if [`PassThroughOneWayPlatform`] has been
214
- /// set to disallow passing through.
215
- ///
216
- /// #### When an entity is known to already be passing through the [`OneWayPlatform`]
217
- ///
218
- /// When an entity begins passing through a [`OneWayPlatform`], it is added to the
219
- /// [`OneWayPlatform`]'s set of active penetrations, and will be allowed to continue
220
- /// to pass through until it is no longer penetrating the platform.
221
- ///
222
- /// #### When an entity is *not* known to be passing through the [`OneWayPlatform`]
223
- ///
224
- /// Depending on the setting of [`PassThroughOneWayPlatform`], the entity may be allowed to
225
- /// pass through.
226
- ///
227
- /// If no [`PassThroughOneWayPlatform`] is present, [`PassThroughOneWayPlatform::ByNormal`] is used.
228
- ///
229
- /// [`PassThroughOneWayPlatform`] may be in one of three states:
230
- /// 1. [`PassThroughOneWayPlatform::ByNormal`]
231
- /// - This is the default state
232
- /// - The entity may be allowed to pass through the [`OneWayPlatform`] depending on the contact normal
233
- /// - If all contact normals are in line with the [`OneWayPlatform`]'s local-space up vector,
234
- /// the entity is allowed to pass through
235
- /// 2. [`PassThroughOneWayPlatform::Always`]
236
- /// - The entity will always pass through the [`OneWayPlatform`], regardless of contact normal
237
- /// - This is useful for allowing an entity to jump down through a platform
238
- /// 3. [`PassThroughOneWayPlatform::Never`]
239
- /// - The entity will never pass through the [`OneWayPlatform`], meaning the platform will act
240
- /// as normal hard collision for this entity
241
- ///
242
- /// Even if an entity is changed to [`PassThroughOneWayPlatform::Never`], it will be allowed to pass
243
- /// through a [`OneWayPlatform`] if it is already penetrating the platform. Once it exits the platform,
244
- /// it will no longer be allowed to pass through.
245
- fn modify_contacts ( & self , contacts : & mut Contacts , commands : & mut Commands ) -> bool {
246
- // This is the contact modification hook, called after collision detection,
247
- // but before constraints are created for the solver. Mutable access to the ECS
248
- // is not allowed, but we can queue commands to perform deferred changes.
249
-
234
+ mut collisions : ResMut < Collisions > ,
235
+ ) {
236
+ // This assumes that Collisions contains empty entries for entities
237
+ // that were once colliding but no longer are.
238
+ collisions. retain ( |contacts| {
250
239
// Differentiate between which normal of the manifold we should use
251
240
enum RelevantNormal {
252
241
Normal1 ,
@@ -255,59 +244,40 @@ impl CollisionHooks for PlatformerCollisionHooks<'_, '_> {
255
244
256
245
// First, figure out which entity is the one-way platform, and which is the other.
257
246
// Choose the appropriate normal for pass-through depending on which is which.
258
- let ( platform_entity, one_way_platform, other_entity, relevant_normal) =
259
- if let Ok ( one_way_platform) = self . one_way_platforms_query . get ( contacts. entity1 ) {
260
- (
261
- contacts. entity1 ,
262
- one_way_platform,
263
- contacts. entity2 ,
264
- RelevantNormal :: Normal1 ,
265
- )
266
- } else if let Ok ( one_way_platform) = self . one_way_platforms_query . get ( contacts. entity2 )
267
- {
268
- (
269
- contacts. entity2 ,
270
- one_way_platform,
271
- contacts. entity1 ,
272
- RelevantNormal :: Normal2 ,
273
- )
247
+ let ( mut one_way_platform, other_entity, relevant_normal) =
248
+ if let Ok ( one_way_platform) = one_way_platforms_query. get_mut ( contacts. entity1 ) {
249
+ ( one_way_platform, contacts. entity2 , RelevantNormal :: Normal1 )
250
+ } else if let Ok ( one_way_platform) = one_way_platforms_query. get_mut ( contacts. entity2 ) {
251
+ ( one_way_platform, contacts. entity1 , RelevantNormal :: Normal2 )
274
252
} else {
275
253
// Neither is a one-way-platform, so accept the collision:
276
254
// we're done here.
277
255
return true ;
278
256
} ;
279
-
280
257
if one_way_platform. 0 . contains ( & other_entity) {
281
258
let any_penetrating = contacts. manifolds . iter ( ) . any ( |manifold| {
282
259
manifold
283
260
. contacts
284
261
. iter ( )
285
262
. any ( |contact| contact. penetration > 0.0 )
286
263
} ) ;
287
-
288
264
if any_penetrating {
289
265
// If we were already allowing a collision for a particular entity,
290
266
// and if it is penetrating us still, continue to allow it to do so.
291
267
return false ;
292
268
} else {
293
269
// If it's no longer penetrating us, forget it.
294
- commands. queue ( OneWayPlatformCommand :: Remove {
295
- platform_entity,
296
- entity : other_entity,
297
- } ) ;
270
+ one_way_platform. 0 . remove ( & other_entity) ;
298
271
}
299
272
}
300
273
301
- match self . other_colliders_query . get ( other_entity) {
274
+ match other_colliders_query. get ( other_entity) {
302
275
// Pass-through is set to never, so accept the collision.
303
276
Ok ( Some ( PassThroughOneWayPlatform :: Never ) ) => true ,
304
277
// Pass-through is set to always, so always ignore this collision
305
278
// and register it as an entity that's currently penetrating.
306
279
Ok ( Some ( PassThroughOneWayPlatform :: Always ) ) => {
307
- commands. queue ( OneWayPlatformCommand :: Add {
308
- platform_entity,
309
- entity : other_entity,
310
- } ) ;
280
+ one_way_platform. 0 . insert ( other_entity) ;
311
281
false
312
282
}
313
283
// Default behaviour is "by normal".
@@ -319,57 +289,16 @@ impl CollisionHooks for PlatformerCollisionHooks<'_, '_> {
319
289
RelevantNormal :: Normal1 => manifold. normal1 ,
320
290
RelevantNormal :: Normal2 => manifold. normal2 ,
321
291
} ;
322
-
323
292
normal. length ( ) > Scalar :: EPSILON && normal. dot ( Vector :: Y ) >= 0.5
324
293
} ) {
325
294
true
326
295
} else {
327
296
// Otherwise, ignore the collision and register
328
297
// the other entity as one that's currently penetrating.
329
- commands. queue ( OneWayPlatformCommand :: Add {
330
- platform_entity,
331
- entity : other_entity,
332
- } ) ;
298
+ one_way_platform. 0 . insert ( other_entity) ;
333
299
false
334
300
}
335
301
}
336
302
}
337
- }
338
- }
339
-
340
- /// A command to add/remove entities to/from the set of entities
341
- /// that are currently in contact with a one-way platform.
342
- enum OneWayPlatformCommand {
343
- Add {
344
- platform_entity : Entity ,
345
- entity : Entity ,
346
- } ,
347
- Remove {
348
- platform_entity : Entity ,
349
- entity : Entity ,
350
- } ,
351
- }
352
-
353
- impl Command for OneWayPlatformCommand {
354
- fn apply ( self , world : & mut World ) {
355
- match self {
356
- OneWayPlatformCommand :: Add {
357
- platform_entity,
358
- entity,
359
- } => {
360
- if let Some ( mut platform) = world. get_mut :: < OneWayPlatform > ( platform_entity) {
361
- platform. 0 . insert ( entity) ;
362
- }
363
- }
364
-
365
- OneWayPlatformCommand :: Remove {
366
- platform_entity,
367
- entity,
368
- } => {
369
- if let Some ( mut platform) = world. get_mut :: < OneWayPlatform > ( platform_entity) {
370
- platform. 0 . remove ( & entity) ;
371
- }
372
- }
373
- }
374
- }
303
+ } ) ;
375
304
}
0 commit comments