Layered Runtime
The crate now has an explicit plugin split:
FpsCameraPluginCore look/orientation runtime. ReadsFpsCameraIntent, applies yaw/pitch and optional free-look offsets, copiesFpsCameraExternalMotion, composesFpsCameraExternalEffects, syncs projection, then writes the final camera transform.FpsCameraLocomotionPluginOptional internal flat-ground motion model. Consumes move/sprint/crouch/jump intent and advances the old built-in locomotion state when no external motion source is active.FpsCameraEffectsPluginOptional presentation/gameplay layer. Consumes runtime motion data to derive bob, landing compression, recoil, trauma shake, lean, viewmodel lag, and landing/footstep messages.FpsCameraLegacyPluginConvenience wrapper that enables all three layers together.
This keeps the default crate entry point a pure camera module while preserving the old full-feature behavior as an explicit opt-in.
Intent To Camera Flow
The public system sets still execute in the same high-level order:
ReadIntentFpsCameraIntentis resolved into yaw/pitch deltas, aim sensitivity scaling, and free-look offsets.UpdateLocomotionThe core copiesFpsCameraExternalMotionwhen present. The optional locomotion plugin may instead advance the internal walk/jump/crouch model.UpdateCameraStateThe core recenters free-look offsets. The optional effects plugin derives gait, landing, trauma decay, tilt, lean, and viewmodel state.ComposeEffectsThe core composes free-look plusFpsCameraExternalEffects. The optional effects plugin can replace that with the richer bob/recoil/landing stack.SyncProjectionThe resolved FOV is written into Bevy’sPerspectiveProjection.SyncTransformThe final render transform is written inPostUpdatebefore transform propagation.
Logical Vs Render Camera
FpsCameraRuntime::position, yaw, and pitch remain the logical state.
Cosmetic motion never mutates that logical look directly. Optional view motion is composed into FpsCameraRuntime::effect_stack, then added to the logical state to produce:
render_translationrender_rotationvisual_fov
That separation keeps gameplay-facing state readable and stable even when presentation becomes aggressive.
The same split applies to first-person props. FpsCameraRuntime::viewmodel_translation and viewmodel_rotation are derived presentation outputs populated by the optional effects plugin; they stay zero in the core-only runtime.
External Motion Seam
FpsCameraExternalMotion is the authoritative seam for external character controllers and physics.
It now carries:
positionvelocitygroundedlanding_impulse- optional
eye_height - optional
crouch_alpha - optional
sprint_alpha
The important distinction is that the core camera no longer simulates gameplay motion by default. If your game already owns locomotion, feed that state directly here and let optional camera layers derive presentation from it.
Internal Locomotion Model
The built-in locomotion path still exists, but only behind FpsCameraLocomotionPlugin / FpsCameraLegacyPlugin.
It is deliberately simple:
- flat ground anchored from the spawn transform
- camera-relative planar intent
- acceleration / deceleration shaping
- sprint and crouch speed scaling
- jump height derived from gravity and desired apex
- grounded / airborne distinction for gravity and air control
This remains useful for examples, tooling, and debug scenes, but it is no longer the default runtime behavior.
Effect Layer Composition
With FpsCameraEffectsPlugin, each motion effect becomes a CameraEffectLayer:
base logical pose
+ head bob / idle sway
+ landing compression
+ trauma shake
+ lean
+ tilt + recoil + free-look offsets
+ external custom effects
= final render posebase logical pose
+ head bob / idle sway
+ landing compression
+ trauma shake
+ lean
+ tilt + recoil + free-look offsets
+ external custom effects
= final render poseThe crate still exposes both CameraEffectLayer and compose_effect_stack() so consumers can extend the additive model without reaching into internal systems.
Extension Guidance
Prefer these seams in order:
- Feed
FpsCameraIntentif you want to drive stock look behavior from a different input source. - Feed
FpsCameraExternalMotionif a separate controller owns movement and grounding. - Add
FpsCameraLocomotionPluginonly if you explicitly want the built-in flat-ground motion model. - Add
FpsCameraEffectsPluginonly if you explicitly want bob/recoil/landing/viewmodel behavior and message-driven presentation. - Feed
FpsCameraExternalEffectsif another system wants custom camera punch, scripted sway, breathing, or custom FOV changes. - Feed
FpsCameraCollisionFeedbackif a physics system detects camera-to-wall collisions.
Avoid writing Transform directly from outside the crate. That bypasses the logical/cosmetic split and makes ordering brittle.