Layers and contact callbacks: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: zone (line 48), movers (line 59), enter (line 98), check (line 107).
1/// A trigger that only cares about one layer, and a listener that hears it.
2///
3/// Quoted by `collision_layers.md` and shown whole in the Source tab.
4library;
6import 'package:flutter3d/flutter3d.dart';
7import 'package:flutter3d_physics/flutter3d_physics.dart';
8import 'package:flutter3d_showcase/src/demo/demo.dart';
9import 'package:vector_math/vector_math.dart';
11/// Which bit means what is a game's own business; `Layers.all` is the only
12/// meaning this package ships. These two are named here, once, for this page.
13const int _kPlayerLayer = 1 << 1;
14const int _kEnemyLayer = 1 << 2;
16/// Records every collider that started overlapping the zone it is attached
17/// to, by name, in the order it happened.
18final class _ZoneListener with CollisionListener {
19 final List<String> started = <String>[];
22 void onCollisionStart(Collider self, Collider other) {
23 started.add(other.userData! as String);
24 }
25}
27final class CollisionLayersDemo extends ShowcaseDemo {
28 late final CollisionWorld _world;
29 late final Collider _player;
30 late final Collider _enemy;
31 final _ZoneListener _listener = _ZoneListener();
33 Vector3 get _zoneCentre => Vector3(0.0, 0.5, 0.0);
34 Vector3 get _away => Vector3(0.0, 0.5, 6.0);
37 void configureView(DemoContext context) {
38 context.orbit
39 ..distance = 5.0
40 ..pitch = 0.2
41 ..yaw = 0.5;
42 }
45 Scene build(DemoContext context) {
46 _world = CollisionWorld();
48 // The zone's mask names one layer, so only a collider carrying that bit
49 // is ever considered for it, whatever mask that collider carries itself.
50 final Collider zone = Collider(
51 shape: CollisionSphere(0.6),
52 position: _zoneCentre,
53 kind: ColliderKind.trigger,
54 mask: _kPlayerLayer,
55 listener: _listener,
56 );
57 _world.add(zone);
59 _player = _world.add(
60 Collider(
61 shape: CollisionBox(Vector3(0.3, 0.3, 0.3)),
62 position: _away,
63 kind: ColliderKind.kinematic,
64 layer: _kPlayerLayer,
65 userData: 'player',
66 ),
67 );
68 _enemy = _world.add(
69 Collider(
70 shape: CollisionBox(Vector3(0.3, 0.3, 0.3)),
71 position: _away,
72 kind: ColliderKind.kinematic,
73 layer: _kEnemyLayer,
74 userData: 'enemy',
75 ),
76 );
77 _world.update();
79 return Scene()
80 ..add(
81 MeshNode(
82 DeviceMesh.upload(
83 context.device,
84 SphereShape(segments: 20, rings: 10).build(),
85 ),
86 Material(name: 'zone', baseColor: Vector4(0.3, 0.7, 0.9, 0.6)),
87 name: 'zone',
88 )..setPositionFrom(_zoneCentre),
89 )
90 ..add(
91 LightNode(name: 'sun', intensity: 2.5)
92 ..setLocalForward(Vector3(-0.3, -0.6, -0.4)),
93 );
94 }
97 void update(DemoContext context, double dt) {
98 // Both walk into the same spot. Only the one carrying the layer the
99 // zone's mask names will be heard.
100 _player.moveTo(_zoneCentre);
101 _enemy.moveTo(_zoneCentre);
102 _world.update();
103 }
106 void verify(Scene scene, FrameResult frame) {
107 if (_listener.started.length != 1 || _listener.started.first != 'player') {
108 throw StateError(
109 'only the player should have entered the zone, got ${_listener.started}',
110 );
111 }
112 if (frame.drawCalls < 1) {
113 throw StateError('the zone did not reach the frame');
114 }
115 }
116}