← API Reference Namespace

asge::game::systems

Classes & Structs

Functions

#void AudioSystem(ecs::Registry &inRegistry, audio::AudioDevice &inDevice)

Drives every entity's AudioSource: starts newly-playing sources, honors PlayAudioSource's restart requests, restarts looping ones that ran out of queued audio, stops ones that finished without looping, and clears data for stopped ones.

A source's audio::AudioStream is created (via inDevice) the first tick it plays and then kept for that AudioSource's lifetime — replaying it (via PlayAudioSource, whether it's still playing or already stopped) always reuses that same stream rather than creating a new one.

#void DebugDrawColliders(ecs::Registry &inRegistry, video::IRenderer &inRenderer) noexcept

Draws every Collider's world-space bounds — Rect via DrawRect, Circle via DrawCircle, both unfilled — color-coded by ResolutionType (green Solid, yellow Trigger, grey Unknown) so a mixed scene reads at a glance which colliders actually push out versus just sense.

Purely opt-in: nothing in the physics/render pipeline calls this on its own, a game calls it from its own Render() (typically behind its own debug toggle) the same as any other system.

#components::ColliderShape WorldBounds(components::Transform const &inTransform, components::Collider const &inCollider) noexcept

inCollider's local shape offset by inTransform's position, in world space — same shape kind, new coordinates.

#void MovementSystem(ecs::Registry &inRegistry, float inDeltaTime) noexcept

Integrates every entity's Velocity into its Transform's position.

Plain Euler integration: position += velocity * inDeltaTime, for every entity with both a Transform and a Velocity. No collision awareness — see CollisionSystem for what runs after this each frame.

#void CollisionSystem(ecs::Registry &inRegistry, PhysicsState &ioState) noexcept

Detects, resolves and reports every Collider overlap this frame.

A QuadTree broad phase feeds a shape-accurate narrow phase. Pairs whose layer/mask can't collide, with an Unknown resolution, or on the same body are skipped. Solid pairs are pushed apart (mass-weighted, only bodies with Velocity+Rigidbody move); Trigger pairs fire Enter/Stay/Exit through ioState once the traversal is over.

#void GravitySystem(ecs::Registry &inRegistry, float inDeltaTime) noexcept

Accelerates every Rigidbody+Velocity entity downward by gravity.

Adds kGravity * inDeltaTime to m_DY for every entity with a Rigidbody whose m_AffectedByGravity is true; entities without a Rigidbody, or with gravity disabled on it, are left untouched.

#void PhysicsUpdate(ecs::Registry &inRegistry, PhysicsState &inState, float inDeltaTime) noexcept

Runs a full physics frame: gravity, movement, path following, then collision detection/response/trigger-events, in that order.

The single entry point a game loop actually needs — see GravitySystem/ MovementSystem/PathFollowingSystem/CollisionSystem for what each step does on its own. inState persists across calls (one per Registry, not shared) so CollisionSystem can tell Enter from Stay from Exit.

#void PathFollowingSystem(ecs::Registry &inRegistry, float inDeltaTime) noexcept

Advances every PathFollow entity's Transform along its resolved components::PathFollow::m_Path, at m_Speed units/second.

Skips an entity whose path has already finished (m_Finished) or whose PathFollow hasn't been resolved into a built path yet (no segments — see asset::Resolver<PathFollow>). Once m_Traveled reaches the path's length, a looping path wraps back to the start; a non-looping one clamps there and sets m_Finished so it's left alone from then on (a zero-length path, e.g. coincident waypoints, always finishes rather than looping, since there's nothing to wrap around). Also orients Transform:: m_LocalRotation to face the path's tangent direction at the new position, except on a degenerate (zero-tangent) segment, where m_LocalRotation is left as it was rather than set to NaN. Writes Transform's Local fields and marks it m_Dirty, same as MovementSystem see TransformPropagationSystem for how that reaches m_WorldCoordinates.

#void AnimationSystem(ecs::Registry &inRegistry, float inDeltaTime) noexcept

Advances every entity's Animation and writes the current frame into its Sprite::m_SourceRect.

Skipped entirely until Animation::m_Clip is resolved (see asset::AssetManager::ResolveAssets) — an entity whose clip hasn't loaded yet, or has no Sprite::m_Texture yet either, is left untouched rather than animated against an empty frame list. Once resolved, accumulates inDeltaTime into Animation::m_ElapsedTime and steps m_CurrentFrame forward once per whole frame duration elapsed (the FrameTable's per-frame duration if it has one, else m_FrameDuration), with elapsed time scaled by m_PlaybackSpeed (a large inDeltaTime can step multiple frames in one call). At the last frame, either wraps to 0 (m_Loop true) or clamps there and clears m_Playing (m_Loop false), emitting events::OnAnimationFinished once for the entity. A non-positive duration or m_PlaybackSpeed is treated as "not animating" rather than risking an infinite advance loop.

#void CameraSystem(ecs::Registry &inRegistry, video::IRenderer &inRenderer, float inDeltaTime) noexcept

Points inRenderer's camera at resources::ActiveCamera's entity, if any.

A no-op (leaving inRenderer's camera exactly as it was) unless ActiveCamera is set to a live entity that carries both a components::Camera and a components::Transform. When it does, sets the renderer's zoom straight from Camera::m_Zoom and centers the viewport on that entity's own Sprite::SpriteGetDstRect midpoint, if it has a Sprite with a resolved texture — not the raw Transform::m_X/m_Y, which is that rect's top-left corner (see SpriteGetDstRect), not its visual middle; centering on the corner would render the sprite offset down-right from screen-center rather than actually centered. Falls back to the raw Transform point for an entity with no Sprite (or an unresolved one) to follow instead. Camera::m_Smoothing == 0 snaps there immediately, a positive value eases toward it exponentially (frame-rate independent — see the .cpp) instead of jumping every frame.

#void RenderSystem(ecs::Registry &inRegistry, video::IRenderer &inRenderer) noexcept

Draws every entity that has both a Transform and a Sprite whose destination rect overlaps the camera's currently visible area (unless it resolves to screen space see below).

Transform::m_WorldCoordinates is the sprite's top-left corner (before rotation — see below); m_WorldScale stretches the drawn size — the texture's native size, or Sprite::m_SourceRect's size when set, so a cropped cell of a larger spritesheet is scaled from its own dimensions rather than the whole sheet's. Entities whose Sprite::m_Texture is null are skipped, as is any world-space entity whose destination rect doesn't overlap IRenderer's current camera/viewport at all (see video::VisibleWorldRect) — cheaper than submitting a draw call the backend would just clip away; a screen-space entity (see below) is never culled this way.

Transform::m_WorldRotation == 0 (the overwhelming majority of sprites) takes IRenderer's plain Rect-based DrawTexture path; a non-zero rotation instead routes through DrawTextureAffine with corners computed by components::SpriteGetDrawCorners, rotating the sprite around its own center (positive m_Rotation is clockwise on screen).

Draw order is sorted, not insertion order, by each entity's resolved components::RenderInfo an entity with none of its own sorts as RenderInfo{} (layer 0, no y-sort, world space). World-space entities draw first, screen-space ones last, under a temporary camera (origin/zoom-1 by default, or resources::ScreenSpaceCamera's own value if that resource is set see its own doc comment for why) restored to the world camera once the last one is drawn; within that, by RenderInfo::m_Layer, then bottom-edge Y (shifted by RenderInfo::m_SortOffsetY) when either side opted into RenderInfo::m_YSort, then by sort owner and m_LocalOrder for entities under a components::Hierarchy parent with m_InheritSortFromParent set, and finally by entity index for a stable order.

Before any of that, a resolved components::UILabel with m_AutoSize writes its own Font::Measure(m_Text) straight into its sibling components::UIRect's m_Size, so this frame's destination rect already reflects the current text rather than whatever size the entity happened to be created with.

#void RenderPipeline(ecs::Registry &inRegistry, video::IRenderer &inRenderer, float inDeltaTime) noexcept

The single per-frame entry point: AnimationSystem, then CameraSystem, then RenderSystem.

Convenience wrapper for callers that want animated, camera-followed sprites without sequencing the three systems themselves — equivalent to calling AnimationSystem(inRegistry, inDeltaTime), then CameraSystem(inRegistry, inRenderer, inDeltaTime), then RenderSystem(inRegistry, inRenderer). CameraSystem must run before RenderSystem so this frame's camera move is what RenderSystem culls and draws against, not last frame's.

#bool IsDrawnAbove(ecs::Registry const &inRegistry, ecs::Entity inA, ecs::Entity inB) noexcept

True if inA sorts after inB in RenderSystem's own resolved draw order i.e.

inA is drawn on top of inB without re-deriving that order from scratch: same resolved RenderInfo (screen-space last, then layer, then y-sort's bottom edge, then sort owner/ local order/inheritance depth), tie-broken by entity index. For a tool (a viewport picker, say) that needs "which of these two would end up on top" outside of an actual draw pass.

An entity with no Sprite/UIRect (nothing RenderSystem would draw) still resolves a layer/screen-space position it just never sorts by y, since there's no dst rect to compute a bottom edge from.

#void TransformPropagationSystem(ecs::Registry &inRegistry) noexcept

Flattens every Transform's Local (authored) state into its World (computed) state, composing through components::Hierarchy where one exists.

A root Transform (no Hierarchy, or one with no parent) only recomputes its World fields when its own m_Dirty is set, copying Local straight across. From there, every descendant with both a Transform and a Hierarchy is composed relative to its parent's just-computed World transform, recursively, whenever the child itself is dirty or an ancestor moved this call — a Hierarchy node with no Transform blocks propagation into its own children, since there's no world transform to compose them against. Every Transform actually recomposed has its m_Dirty cleared before returning.

#void UIInteractionSystem(ecs::Registry &inReg, input::InputState const &inInput, video::Camera const &inCamera)

Resolves this frame's hover/click against resources::UIHitList, and updates every Interactable's m_Hovered/m_Held/m_Clicked from it.

No-ops if the UIHitList resource hasn't been set (see its own doc comment) so this is safe to call unconditionally even in states that don't use UI. Walks the hit list back to front so a topmost/overlapping entity wins. A disabled Interactable (m_Enabled false) is forced hovered/held false and never wins a click, regardless of what CollectHitList did or didn't put in the hit list for it. Also fires the sibling UIButton's m_OnClick, if any, on the same held-to-released-while- hovered edge that sets m_Clicked.

#void UILayoutSystem(ecs::Registry &inReg) noexcept

Places and resizes each UIPanel's children (via Hierarchy, in sibling order) into the slots of its Grid/VStack/HStack layout, inset by margin + padding with m_Spacing between slots.

Children are moved (Transform::m_LocalCoordinates, marked dirty) and their UIRect stretched to fill the slot, or per axis as their UILayoutItem says (fill/keep size, start/center/end alignment). Sprites and auto-sized labels never stretch. Nested panels are laid out after their parent. Absolute panels and children past the last slot are left alone. Run it before TransformPropagationSystem.