bevy-tnua 0.32.0

A floating character controller for Bevy
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# Migrating to Tnua 0.30

`TnuaSimpleAirActionsCounter` is deprecated, and needs to be replaced by
`TnuaActionsCounter`.

Say you have this:

```rust
#[derive(TnuaScheme)]
#[scheme(basis = TnuaBuiltinWalk)]
enum ControlScheme {
    Jump(TnuaBuiltinJump),
    Dash(TnuaBuiltinDash),
    Crouch(TnuaBuiltinCrouch),
}
```

Previously you'd have to implement:
```rust
impl TnuaAirActionDefinition {
    fn is_air_action(action: Self::ActionDiscriminant) -> bool {
        match action {
            ControlSchemeActionDiscriminant::Jump => true,
            ControlSchemeActionDiscriminant::Dash => true,
            ControlSchemeActionDiscriminant::Crouch => false,
        }
    }
}
```

And then add a `TnuaSimpleAirActionsCounter<ControlScheme>` component and call
its `update` every frame. This is deprecated.

Instead, define a slots struct:

```rust
#[derive(TnuaActionSlots)]
#[slots(scheme = ControlScheme)]
struct AirActionSlots {
    #[slots(Jump)]
    jump: usize,
    #[slots(Dash)]
    dash: usize,
    // `Crouch` is not assigned to any slot because it's not an air action.
}
```

Instead of having a single counter for all the air actions, each slot is
counted separately. A slot can have multiple actions assigned to it, so if you
want the old behavior (air jumps and air dashes are both counted as the same
resource) you can just define a single slot.

With `TnuaSimpleAirActionsCounter` you'd call `update` manually. Here, you should instead use a plugin:

```rust
app.add_plugins(TnuaAirActionsPlugin::<AirActionSlots>::new(
    // This has to be the same schedule `TnuaControllerPlugin` was registered with
    FixedUpdate,
));
```

This will add `TnuaActionsCounter<AirActionSlots>` as the dependency of
`TnuaController<ControlScheme>` and also add a system that automatically calls
`TnuaActionsCounter::update`.

`TnuaActionsCounter::count_for` is used exactly like
`TnuaSimpleAirActionsCounter::air_count_for`.

# Migrating to Tnua 0.28

* Instead of adding the configuration with `TnuaController::new`, add it as a
  new component - `TnuaConfig`:
  ```rust
  // Old way
  cmd.insert(
      TnuaController::<ControlScheme>::new(asset_server.load("configuration.ron")),
  );

  // New way
  cmd.insert((
      TnuaController::<ControlScheme>::default(),
      TnuaConfig::<ControlScheme>(asset_server.load("configuration.ron")),
  ));
  ```
* The `sensors_entities` field of the controller was moved to a new component -
  `TnuaSensorsEntities`. This component is added automatically via Bevy's
  required components mechanism, so you don't need to add it yourself, but if
  you access it in your system you need to request it separately in the query.

# Migrating to Tnua 0.27 ("Schemes")

## Static definition of supported basis and actions

The biggest change is that instead of dynamically feeding basis and actions to
the controller, you must now define a control scheme which statically defines
the basis and actions your controller can use. This is done like so:

```rust
#[derive(TnuaScheme)]
#[scheme(basis = TnuaBuiltinWalk)]
enum ControlScheme {
    Jump(TnuaBuiltinJump),
    Crouch(TnuaBuiltinCrouch),
    DifferentKindOfJump(TnuaBuiltinJump),
}
```

Both `TnuaController` and `TnuaControllerPlugin` are parameterized by the
control scheme. `TnuaController` no longer implement `Default` - instead it has
a `new` method that accepts an handle to a config asset handle. The
configuration struct is defined automatically by the `TnuaScheme` macro, and
you can either load it from a file (it implements `Serialize` and
`Deserialize`, but it's up to you to handle the loading itself - e.g. with
another plugin) or inject it directly to the `Assets` resource. The generated
configuration struct's name is the name of the control scheme enum plus the
`Config` suffix.

```rust
cmd.insert(TnuaController::<ControlScheme>::new(
    control_scheme_configs.add(ControlSchemeConfig {
        basis: TnuaBuiltinWalkConfig {
            ..Default::default()
        },
        jump: TnuaBuiltinJumpConfig {
            ..Default::default()
        },
        crouch: TnuaBuiltinCrouchConfig {
            ..Default::default()
        },
        different_kind_of_jump: TnuaBuiltinJumpConfig {
            ..Default::default()
        },
    })
));
```

Feeding the basis is now done by setting a `pub` field of the controller named
`basis`. Feeding the action is still done with the `action` method, but instead
of the action type itself it receives an instance of the control scheme type
(the variant decieds the action)

## The schedules of action feeding

Tnua no longer demands for user input to be done in the same schedule as the
Tnua systems (though `TnuaControllerPlugin` itself still needs to run in a
schedule of the same frequency as the physics backend). **The downside** of
this is that Tnua now expects to be informed about this by invoking, in each
frame before feeding any action, the controller's `initiate_action_feeding`
method. Otherwise `action` will panic. This method must be invoked regardless
of whether or not actions are actually fed, because Tnua uses it to know that
an action was not fed and it can finish it.

This is an issue for actions like `TnuaBuiltinKnockback` that are invoked as a
response for external events. For this, other action feeding methods can be
used:

* `action_trigger` - for actions that don't care how long the button is
  pressed. For example - a dash.
* `action_interrupt` - similar to `action_trigger`, but will override any other
  action contender (in the future maybe even running actions) and cannot be
  overridden itself. This is for things like `TnuaBuiltinKnockback` that are
  forced upon the character.
* `action_start` and `action_end` - for feeding when the button is pressed and
  released, without worrying about the frames in-between.

## Other actionable changes of note

### Basis and action structure and terminology

- The configuration was used to be part of the action struct. Now it is split
  to a `Config` struct, and the action struct is called "input".
- The `State` property of basis and actions is now named `Memory`. This is
  mostly important for those writing custom basis/actions, but also for those
  who inspect or modify that internal data (e.g. to decide the animation)
- The term `State` now means the triplet of input, config and memory (and
  sometimes it contains payloads)

### Accessing current basis and action data

- Basis and actions no longer have `&'static str` names - the basis will not
  change and the action is identified by the variant. The `TnuaScheme` derive
  generates an enum with the name of the control scheme enum plus the
  `ActionDiscriminant` suffix, and there are methods like `discriminant` on the
  input/state and `action_discriminant` on the controller for querying it.
- `concrete_basis` is no longer needed - instead, to access the basis' state,
  the controller has the direct fields `basis` (the input), `basis_config` (a
  copy of the configuration part from the asset) and `basis_memory`.
- `concrete_action` no longer exists - instead access the `current_action`
  field directly. That field is of an enum type generated by the `TnuaScheme`
  derive, named after the control scheme enum plus the `ActionState` suffix. It
  contains the action's state (input, config, and memory)

### Sensors (and ghost sensors)

- Sensors are no longer on the same entity as the controller. To get the entity
  that represents a sensor, use the `entities` field of the controller.
  Different basis may result in different structure of sensors and thus
  different sensors `Entities` struct. These entities will get created
  automatically by the controller based on the basis' definition.
- `TnuaGhostSensor` needs to be on the sensor entity, not the controller
  entity. If you add the `TnuaGhostOverwrites` component to the controller
  entity, it'll automatically add `TnuaGhostSensor` to its (relevant) sensors.
- Overwriting the `output` field of the `TnuaProximitySensor` no longer works.
  Instead, use the `set` method of `TnuaGhostOverwrite` (the fields of
  `TnuaGhostOverwrites` should be of that type)

### Various helpers

- `TnuaCrouchEnforcer` is gone. `TnuaBuiltinCrouch` can do what it did by
  itself now - but only if the `headroom` field is properly configured in
  `TnuaBuiltinWalkConfig`.
- `TnuaSimpleAirActionsCounter` now accepts the control scheme as a generic
  parameter - and you need to implement `TnuaAirActionDefinition` for your
  scheme.
- `TnuaBlipReuseAvoidance` now accepts the control scheme as a generic
  parameter - and you need to implement `TnuaHasTargetEntity` for your scheme.
  Note that the entities are no longer part of the actions - they should be
  part of the payloads.

# Migrating to Tnua 0.16

All plugins now support specifying a schedule, which means that they are no
longer unit structs. They all now implement `Default` to retain the same
behavior, and have a `new()` method that accepts a Bevy `ScheduleLabel`.

Only use `new()` if you are changing the scheduling of of the physics backend:

- When using Rapier's `in_fixed_schedule()` or XPBD's equivalent
  `PhysicsPlugins::new(FixedUpdate)`, use `new(FixedUpdate)` for the Tnua
  plugins.
- When using XPBD's `Physics::fixed_hz` timer, use `new(PhysicsSchedule)` for
  the Tnua plugins.
- Either way - add the player controller system in the same schedule as the
  Tnua plugins (defaults to `Update`)

# Migrating to Tnua 0.13

- Instead of using the `rapier_2d` or the `rapier_3d` features, add the
  `bevy-tnua-rapier2d` or the `bevy-tnua-rapier3d` crate.
- The `TnuaRapier2d*` or `TnuaRapier3d*` components are no longer in
  `bevy_tnua::prelude`. They should be imported from the new crates.

# Migrating to Tnua 0.10

## Basic ECS initialization

- Instead of `TnuaPlatformerPlugin`, add `TnuaControllerPlugin`.
- Instead of `TnuaPlatformerBundle`, add `TnuaControllerBundle`. Note that
  `TnuaControllerBundle` does not need to be configured - the entire
  configuration is passed with the basis and the actions.

## Character control

Controls should still be passed every frame, preferably
`in_set(TnuaUserControlsSystemSet)`. Instead of using the
`TnuaPlatformerControls` component, the controls should be passed to the
`TnuaController` component.

### Base movement

Instead of changing `TnuaPlatformerControls::desired_velocity`, pass the
`TnuaBuiltinWalk` basis to `TnuaController` and set the `desired_velocity`
there:
```rust
controller.basis(TnuaBuiltinWalk {
    desired_velocity: the_desired_velocity,
    float_height: 2.0, // must be passed as well in this new scheme
    ..Default::default()
});
```
* Note that there is no longer a separate `full_speed` configuration field.
  `TnuaBuiltinWalk::desired_velocity` represents the speed as well as the
  direction.

### Jumping

Instead of changing `TnuaPlatformerControls::jump`, use the `TnuaBuiltinJump`
action:
```rust
if should_jump {
    controller.action(TnuaBuiltinJump {
        height: 4.0,
        ..Default::default()
    });
}
```
* Note that there is no longer a separate `full_jump_height` configuration
  field. `TnuaBuiltinJump::height`, which is part of the action and gets passed
  on each frame, controls it.
* When the action is no longer fed, the jump is stopped. There is no need to
  manually nullify the command.

### Crouching

To crouch, instead of changing the
`TnuaPlatformerControls::float_height_offset`, use the `TnuaBuiltinCrouch`
action.
```rust
if should_crouch {
    controller.action(TnuaBuiltinCrouch {
        float_offset: -0.9,
        ..Default::default()
    });
}
```
* Instead of `TnuaKeepCrouchingBelowObstacles`, use `TnuaCrouchEnforcer`. See
  its documentation regarding its usage.

### Turning

Tnua no longer turns the character automatically according to the movement
direction. `TnuaBuiltinWalk::desired_forward` must be passed manually in order
to make the character turn.

`TnuaManualTurningOutput` was completely removed, without being replaced by
anything. There is no longer a reason for Tnua to manage this kind of manual
turning. Just use some tweening plugin.

The forward direction is **always** assumed to be the negative Z axis. If there
is some other forward direction, the game must do the math itself and set
`desired_forward` to set the negative Z axis of the model to the correct
direction.

## Animating

`TnuaPlatformerAnimatingOutput` is removed. Instead, the animating data should
be taken directly from the `TnuaController`.

The state of the basis can be retrieved with `TnuaController::concrete_basis`:

```rust
let Some((basis_input, basis_state)) = controller.concrete_basis::<TnuaBuiltinWalk>()
else {
    // in case some other basis is used - usually happens before the first basis is fed.
    continue;
};

// basis_input is the `TnuaBuiltinWalk` command, which is usually not very interesting.

// This replaces `let speed = animating_output.running_velocity.length();`:
let speed = basis_state.running_velocity.length();
```

Many games would only ever use one basis (probably `TnuaBuiltinWalk`), but a
game that uses multiple bases (e.g. - different bases for walking and for
swiming) can retrieve the name of the current basis by using the
`TnuaController::basis_name` method. This returns a string that can be
`match`ed on, which should be faster than trying multiple downcasts with
`concrete_basis`.

Actions, similarly, have `TnuaController::concrete_action` and
`TnuaController::action_name` (which is more useful than `basis_name`, because
it should be more common for games to use multiple actions, and because action
may play animation based on the action name only, regardless of the action
state)

For both bases and actions, when matching on `basis_name`/`action_name`, prefer
using the `NAME` constant of the basis/action over a string literal:

```rust
match controller.action_name() {
    Some(TnuaBuiltinJump::NAME) => { /* ... */ }
    Some(TnuaBuiltinCrouch::NAME) => { /* ... */ }
    Some(other) => panic!("Unknown action {other}"),
    None => { /* ... */ }
}
```

Of course, when using `named_basis`/`named_action` it is perfectly acceptable
to use the string literal.

## Storing configuration as data

There is no longer `TnuaPlatformerConfig` component, but it may still be
desirable to store a character's movement configuration in the ECS. The
simplest way to do this is to store the basis and the actions themselves in the
ECS, and clone them into the controller when they are needed.

In the examples, this is done by declaring a new component:
```rust
#[derive(Component)]
pub struct CharacterMotionConfigForPlatformerDemo {
    pub speed: f32,
    pub walk: TnuaBuiltinWalk,
    pub jump: TnuaBuiltinJump,
    pub crouch: TnuaBuiltinCrouch,
}
```

which is then added to the character entity:

```rust
cmd.insert(CharacterMotionConfigForPlatformerDemo {
    speed: 40.0,
    walk: TnuaBuiltinWalk {
        float_height: 2.0,
        ..Default::default()
    },
    jump: TnuaBuiltinJump {
        height: 4.0,
        ..Default::default()
    },
    crouch: TnuaBuiltinCrouch {
        float_offset: -0.9,
        ..Default::default()
    },
});
```

Note that since `TnuaBuiltinWalk::desired_velocity` is a vector, and the
configuration should only store its magnitude - not direction - we store the
`speed` as a separate field.

Then, in the controls system, the basis and actions can simply be `clone`d:

```rust
controller.basis(TnuaBuiltinWalk {
    desired_velocity: direction * config.speed,
    ..config.walk.clone()
});

if crouch {
    controller.action(config.crouch.clone());
}

if jump {
    controller.action(config.jump.clone());
}
```