A proxy is what the broad phase will index: an actor's shape, where it is, and the bounds that follow from those. It is a sixth heap layer, two per actor -- one for the actor itself and headroom for static geometry a game registers that is not tile-aligned. Solid *tiles* deliberately get none: the broad phase will read those out of the tilemap's own array, because one proxy per tile is tens of megabytes to index data that is already a grid. The pool follows the existing convention rather than improving on it. akgl_heap_next_collision_proxy finds a free slot and does not claim it; akgl_collision_proxy_initialize claims it. That is TODO.md item 8's asymmetry, and it is kept on purpose: an acquire abandoned before initialization leaks nothing, because the slot still reads as free, and a sixth pool with its own rule would be worse than one defect with five instances. Fixing item 8 means fixing all five together with every call site audited, and that is its own change. The proxy holds a *copy* of the shape rather than a pointer. An actor's own logic may rewrite its shape mid-step -- a character crouching, a projectile arming -- and a broad phase whose bounds came from a shape that has since changed is a bug that only appears when two things happen in the same frame. akgl_heap_release_actor now releases the actor's proxy. Without it the proxy holds a borrowed `owner` into a slot that has just been zeroed, so the broad phase keeps a registration whose owner reads as a free actor, and the next contact against it is a collision with nothing. Verified by removing the release and watching test_proxy_dies_with_its_actor go red. The tests pin the convention as well as the behaviour: that two acquires with no initialize between them return the same slot is asserted, not merely tolerated, so that changing the convention has to change a test that says why it exists. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
459 lines
29 KiB
C
459 lines
29 KiB
C
/**
|
|
* @file actor.h
|
|
* @brief A live thing in the world: state bits, motion, hierarchy, and behaviour hooks.
|
|
*
|
|
* An actor is an instance; the akgl_Character it points at is its template. The
|
|
* actor carries only what is unique to it -- where it is, which way it is
|
|
* moving, which animation frame it is on -- and borrows speed, acceleration and
|
|
* the state-to-sprite map from the character. That is what makes a hundred of
|
|
* one kind of thing cheap.
|
|
*
|
|
* **State is a bitmask**, not an enum. An actor is facing left *and* moving left
|
|
* *and* alive at the same time, and the whole combination is the key that
|
|
* selects a sprite (see akgl_character_sprite_get). Adding a state means adding
|
|
* a bit here *and* a name in `src/actor_state_string_names.c`, or character JSON
|
|
* cannot refer to it.
|
|
*
|
|
* **Behaviour attaches as function pointers**, not by inheritance. Every actor
|
|
* gets the library's default `updatefunc`, `renderfunc`, `facefunc`,
|
|
* `movementlogicfunc`, `changeframefunc` and `addchild` from
|
|
* akgl_actor_initialize; a game that wants a different flavour of anything
|
|
* replaces the pointer on that one actor.
|
|
*
|
|
* **Motion is split four ways** -- thrust, environmental, acceleration, and the
|
|
* velocity that is their sum -- so that a character's own walking and the
|
|
* world's gravity and drag can be reasoned about separately. See physics.h for
|
|
* the model and which fields mean what.
|
|
*
|
|
* Actors are pool objects (akgl_heap_next_actor) published in
|
|
* #AKGL_REGISTRY_ACTOR under their name.
|
|
*/
|
|
|
|
#ifndef _AKGL_ACTOR_H_
|
|
#define _AKGL_ACTOR_H_
|
|
|
|
#include <stdint.h>
|
|
#include <akgl/types.h>
|
|
#include <akgl/character.h>
|
|
|
|
// ---- LOW WORD STATUSES ----
|
|
//
|
|
// The trailing column is the bit pattern within its own 16-bit half -- the
|
|
// low half here, the high half below -- not the full 32-bit value. Read as a
|
|
// whole word it looks like bit 16 restarts at bit 0, which it does not.
|
|
|
|
#define AKGL_ACTOR_STATE_FACE_DOWN (1 << 0) // 1 0000 0000 0000 0001
|
|
#define AKGL_ACTOR_STATE_FACE_LEFT (1 << 1) // 2 0000 0000 0000 0010
|
|
#define AKGL_ACTOR_STATE_FACE_RIGHT (1 << 2) // 4 0000 0000 0000 0100
|
|
#define AKGL_ACTOR_STATE_FACE_UP (1 << 3) // 8 0000 0000 0000 1000
|
|
#define AKGL_ACTOR_STATE_ALIVE (1 << 4) // 16 0000 0000 0001 0000
|
|
#define AKGL_ACTOR_STATE_DYING (1 << 5) // 32 0000 0000 0010 0000
|
|
#define AKGL_ACTOR_STATE_DEAD (1 << 6) // 64 0000 0000 0100 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_LEFT (1 << 7) // 128 0000 0000 1000 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_RIGHT (1 << 8) // 256 0000 0001 0000 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_UP (1 << 9) // 512 0000 0010 0000 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_DOWN (1 << 10) // 1024 0000 0100 0000 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_IN (1 << 11) // 2048 0000 1000 0000 0000
|
|
#define AKGL_ACTOR_STATE_MOVING_OUT (1 << 12) // 4096 0001 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_13 (1 << 13) // 8192 0010 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_14 (1 << 14) // 16384 0100 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_15 (1 << 15) // 32768 1000 0000 0000 0000
|
|
|
|
// ----- HIGH WORD STATUSES -----
|
|
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_16 (1 << 16) // 65536 0000 0000 0000 0001
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_17 (1 << 17) // 131072 0000 0000 0000 0010
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_18 (1 << 18) // 262144 0000 0000 0000 0100
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_19 (1 << 19) // 524288 0000 0000 0000 1000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_20 (1 << 20) // 1048576 0000 0000 0001 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_21 (1 << 21) // 2097152 0000 0000 0010 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_22 (1 << 22) // 4194304 0000 0000 0100 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_23 (1 << 23) // 8388608 0000 0000 1000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_24 (1 << 24) // 16777216 0000 0001 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_25 (1 << 25) // 33554432 0000 0010 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_26 (1 << 26) // 67108864 0000 0100 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_27 (1 << 27) // 134217728 0000 1000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_28 (1 << 28) // 268435456 0001 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_29 (1 << 29) // 536870912 0010 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_30 (1 << 30) // 1073741824 0100 0000 0000 0000
|
|
#define AKGL_ACTOR_STATE_UNDEFINED_31 (1 << 31) // 2147483648 1000 0000 0000 0000
|
|
|
|
/** @brief Bits in an actor's state word. Fixed by the width of `int32_t state`. */
|
|
#define AKGL_ACTOR_MAX_STATES 32
|
|
|
|
/**
|
|
* @brief Bit position -> state name, as text. Index `i` names the bit `1 << i`.
|
|
*
|
|
* akgl_registry_init_actor_state_strings builds
|
|
* #AKGL_REGISTRY_ACTOR_STATE_STRINGS out of this, which is what lets character
|
|
* JSON write `"AKGL_ACTOR_STATE_FACE_LEFT"` instead of `2`. A bit whose name
|
|
* here does not match its `#define` above cannot be referred to from JSON at
|
|
* all -- which was the case for `MOVING_IN` and `MOVING_OUT` until 0.5.0.
|
|
*
|
|
* Maintained by hand in `src/actor_state_string_names.c`, and exactly
|
|
* #AKGL_ACTOR_MAX_STATES entries long: it was declared one longer than it was
|
|
* defined until 0.5.0, so a consumer trusting the declared bound read past the
|
|
* object. `tests/registry.c` checks every entry against its bit.
|
|
*/
|
|
extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES];
|
|
|
|
/** @brief Every facing bit. Clear this before setting one, so an actor faces exactly one way. */
|
|
#define AKGL_ACTOR_STATE_FACE_ALL (AKGL_ACTOR_STATE_FACE_DOWN | AKGL_ACTOR_STATE_FACE_LEFT | AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_FACE_UP)
|
|
/** @brief Every planar movement bit. The two depth bits, MOVING_IN and MOVING_OUT, are not in it. */
|
|
#define AKGL_ACTOR_STATE_MOVING_ALL (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_MOVING_UP | AKGL_ACTOR_STATE_MOVING_DOWN)
|
|
|
|
/** @brief Longest actor name, including the terminator. Names are truncated, not rejected. */
|
|
#define AKGL_ACTOR_MAX_NAME_LENGTH 128
|
|
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
|
|
#define AKGL_ACTOR_MAX_CHILDREN 8
|
|
|
|
|
|
/** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */
|
|
typedef struct akgl_Actor {
|
|
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. One reference per parent, plus one for the registry. */
|
|
char name[AKGL_ACTOR_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
|
|
akgl_Character *basechar; /**< The template this actor instantiates. Borrowed; no reference is taken. Required before the actor can update, render, or simulate. */
|
|
uint8_t curSpriteFrameId; /**< Index into the current sprite's `frameids`, not a frame number on the sheet. */
|
|
SDL_Time curSpriteFrameTimer; /**< When the current frame was shown. The next frame is due once the sprite's `speed` has elapsed. */
|
|
bool curSpriteReversing; /**< Walking the animation backwards, for a sprite with `loopReverse` set. */
|
|
uint32_t layer; /**< Which tilemap layer the actor is drawn and simulated on. Set from the object layer it was placed in. */
|
|
int32_t state; /**< The `AKGL_ACTOR_STATE_*` bitmask. The whole value is the key that selects a sprite. */
|
|
bool movement_controls_face; /**< When set, the default `facefunc` turns the actor to face whichever way it is moving. Clear it for an actor that aims independently. */
|
|
void *actorData; /**< The game's own per-actor data. Never read or freed by the library. */
|
|
bool visible; /**< Whether to draw at all. An actor off-camera is skipped separately, so this is for deliberate hiding. */
|
|
SDL_Time movetimer; /**< Unused. Nothing in the library reads or writes it. */
|
|
akgl_CollisionShape shape; /**< The collision volume this actor occupies. Copied from the character by akgl_actor_set_character unless #shape_override is set. Zeroed by akgl_actor_initialize, so an actor nobody configured does not collide. */
|
|
bool shape_override; /**< The game set #shape itself and akgl_actor_set_character must leave it alone. Distinct from "the shape is empty": an actor deliberately given no collider and an actor not yet given one are different states, and only this flag tells them apart. */
|
|
struct akgl_CollisionProxy *proxy; /**< This actor's registration in the broad phase, or `NULL` when it has none. Owned by the library and released with the actor; a game neither sets nor frees it. */
|
|
// Velocity. Combined effect of all forces acting on the actor resulting
|
|
// in energy along an axis.
|
|
float32_t vx; /**< Velocity along x, world units per second. Recomputed each step as `ex + tx`; writing it directly is overwritten. */
|
|
float32_t vy; /**< Velocity along y. On a child actor this is read as an offset from the parent instead. */
|
|
float32_t vz; /**< Velocity along z. */
|
|
// Environmental velocity. These are the forces acting on the actor by the
|
|
// environment (such as gravity and atmospheric drag)
|
|
float32_t ex; /**< Environmental velocity along x: what gravity and drag have done. Accumulates across steps. */
|
|
float32_t ey; /**< Environmental velocity along y. This is what carries a falling actor. */
|
|
float32_t ez; /**< Environmental velocity along z. */
|
|
// Thrust. Energy originating only from the actor's own acceleration on a
|
|
// given axis, before the effects of gravity and drag.
|
|
float32_t tx; /**< Thrust along x: the actor's own effort. Capped at `sx`, which is why gravity can outrun top speed and walking cannot. */
|
|
float32_t ty; /**< Thrust along y, capped at `sy`. */
|
|
float32_t tz; /**< Thrust along z, capped at `sz`. */
|
|
// Acceleration. These are borrowed from the base character object.
|
|
// A given axis resets to 0 when the actor stops moving in a given axis.
|
|
float32_t ax; /**< Acceleration along x, signed by the direction of travel. Copied from the character each step by the default movement logic. */
|
|
float32_t ay; /**< Acceleration along y, signed the same way. */
|
|
float32_t az; /**< Acceleration along z. Not set by the default movement logic. */
|
|
// Max speed. These are borrowed from the base character object.
|
|
float32_t sx; /**< Maximum thrust along x, copied from the character. */
|
|
float32_t sy; /**< Maximum thrust along y. */
|
|
float32_t sz; /**< Maximum thrust along z. */
|
|
// Position.
|
|
float32_t x; /**< Position in map pixels. For a child, an offset from the parent. */
|
|
float32_t y; /**< Position in map pixels. Also what the tilemap's perspective band scales against. */
|
|
float32_t z; /**< Depth. Carried through the simulation but not used at draw time. */
|
|
float32_t scale; /**< Draw scale, 1.0 for natural size. Written by akgl_tilemap_scale_actor, or forced to 1.0 when tilemap scaling is off. */
|
|
struct akgl_Actor *children[AKGL_ACTOR_MAX_CHILDREN]; /**< Attached actors, `NULL` in unused slots. Each holds a reference and is released with the parent. */
|
|
struct akgl_Actor *parent; /**< The actor this one is attached to, or `NULL`. A child is positioned relative to it and does not simulate on its own. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*updatefunc)(struct akgl_Actor *obj); /**< Per-frame logic: facing, then animation frame. Defaults to akgl_actor_update. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*renderfunc)(struct akgl_Actor *obj); /**< Per-frame draw. Defaults to akgl_actor_render. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*facefunc)(struct akgl_Actor *obj); /**< Chooses the facing bits. Defaults to akgl_actor_automatic_face. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*movementlogicfunc)(struct akgl_Actor *obj, float32_t dt); /**< Called by the physics step before gravity. Defaults to akgl_actor_logic_movement. May raise AKGL_ERR_LOGICINTERRUPT to skip the rest of the step. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*changeframefunc)(struct akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems); /**< Advances the animation. Defaults to akgl_actor_logic_changeframe. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*addchild)(struct akgl_Actor *obj, struct akgl_Actor *child); /**< Attaches a child. Defaults to akgl_actor_add_child. */
|
|
} akgl_Actor;
|
|
|
|
/**
|
|
* @brief Zero a pooled actor, name it, wire up its default behaviour, and register it.
|
|
*
|
|
* Sets `scale` to 1.0 and `movement_controls_face` to true, installs all six
|
|
* default function pointers, publishes the actor in #AKGL_REGISTRY_ACTOR, and
|
|
* takes the first reference. It does *not* set a character: the actor cannot
|
|
* update, render, or simulate until akgl_actor_set_character has run.
|
|
*
|
|
* @param obj Pooled actor to initialize, normally from akgl_heap_next_actor.
|
|
* Required. Any previous contents are discarded -- including its
|
|
* child list, so release an actor rather than reinitializing it.
|
|
* @param name Registry key, NUL-terminated. Required. Truncated at
|
|
* #AKGL_ACTOR_MAX_NAME_LENGTH. An existing entry with the same name
|
|
* is silently replaced, and the actor it displaced becomes
|
|
* unreachable rather than being released.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p name is `NULL`.
|
|
* @throws AKERR_KEY If the actor cannot be written into #AKGL_REGISTRY_ACTOR --
|
|
* in practice, because akgl_registry_init has not run.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_initialize(akgl_Actor *obj, char *name);
|
|
/**
|
|
* @brief Bind an actor to a registered character, and copy its top speeds.
|
|
*
|
|
* Looks the character up by name and takes its `sx`/`sy` as this actor's speed
|
|
* limits, zeroing `ax` and `ay` so the actor starts from rest. The character is
|
|
* borrowed, not referenced: releasing it out from under a live actor leaves a
|
|
* dangling pointer.
|
|
*
|
|
* @param obj The actor to bind. Required.
|
|
* @param basecharname Registry name of the character. Required. Rebinding an
|
|
* actor mid-life is allowed, and is how a character swaps
|
|
* its whole sprite set at once.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p basecharname is `NULL`, or if no
|
|
* character is registered under that name. The last case is a lookup
|
|
* miss reported with a pointer status rather than AKERR_KEY.
|
|
*
|
|
* @note `sz` is not copied and `az` is not zeroed, so an actor's depth speed
|
|
* keeps whatever it had. The default movement logic re-copies all three
|
|
* each step, which papers over it for anything that simulates.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_set_character(akgl_Actor *obj, char *basecharname);
|
|
/**
|
|
* @brief Draw an actor's current animation frame, if it is on camera.
|
|
*
|
|
* Selects the sprite for the actor's current state, checks it against the
|
|
* camera, and blits the frame at the actor's position translated into screen
|
|
* space. A child actor is drawn at its parent's position plus its own, which is
|
|
* what makes an offset mean an offset.
|
|
*
|
|
* Several things are skipped rather than reported, because a frame is not the
|
|
* place to fail: an actor with no sprite for its state, one off camera, one with
|
|
* `visible` clear, and one whose frame index has run past its current sprite
|
|
* are all simply not drawn.
|
|
*
|
|
* @param obj The actor to draw. Required, along with its `basechar`.
|
|
* @return `NULL` on success -- including every skipped case above -- otherwise
|
|
* an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
|
|
* @throws AKERR_* Whatever akgl_spritesheet_coords_for_frame or the renderer's
|
|
* `draw_texture` raises.
|
|
*
|
|
* @note The destination height is computed from the sprite's *width*, so a
|
|
* non-square sprite is drawn square.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_render(akgl_Actor *obj);
|
|
/**
|
|
* @brief Per-frame logic: turn the actor to face its direction, then advance its animation.
|
|
*
|
|
* Calls the actor's `facefunc`, then selects the sprite for the resulting state
|
|
* and calls `changeframefunc` if that sprite's frame time has elapsed. An actor
|
|
* with no sprite registered for its current state is left alone and reported as
|
|
* success -- states change faster than art gets drawn, and a missing sprite
|
|
* should not stop the frame.
|
|
*
|
|
* @param obj The actor to update. Required, along with its `basechar`.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
|
|
* @throws AKERR_* Whatever `facefunc` or `changeframefunc` raises. Note that an
|
|
* AKERR_KEY from either is swallowed along with the missing-sprite case.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_update(akgl_Actor *obj);
|
|
/**
|
|
* @brief The default `movementlogicfunc`: turn movement bits into signed acceleration.
|
|
*
|
|
* Re-copies the character's speed limits onto the actor -- so a change to the
|
|
* character takes effect next step -- and sets `ax`/`ay` to plus or minus the
|
|
* character's acceleration according to which movement bits are set. It does not
|
|
* integrate anything; the physics backend does that.
|
|
*
|
|
* Opposing bits do not cancel: `MOVING_LEFT` wins over `MOVING_RIGHT` because it
|
|
* is tested first. An actor with no movement bits keeps its previous
|
|
* acceleration rather than being zeroed, which is why the input handlers clear
|
|
* it themselves on key release.
|
|
*
|
|
* @param obj The actor to compute acceleration for. Required, along with its
|
|
* `basechar`.
|
|
* @param dt Seconds since the previous step. Accepted for the hook's signature;
|
|
* this implementation does not use it, since it sets an acceleration
|
|
* rather than integrating one.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
|
|
*
|
|
* @note A replacement for this hook may raise AKGL_ERR_LOGICINTERRUPT to tell
|
|
* the physics step to skip the rest of this actor's tick. This
|
|
* implementation never does.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_movement(akgl_Actor *obj, float32_t dt);
|
|
/**
|
|
* @brief The default `changeframefunc`: step to the next animation frame.
|
|
*
|
|
* Walks forward through the sprite's frames; at the last one it either wraps to
|
|
* 0, or -- for a sprite with both `loop` and `loopReverse` -- turns round and
|
|
* walks back down, turning again at frame 0. A sprite with `loop` clear also
|
|
* wraps to 0 rather than holding the final frame.
|
|
*
|
|
* @param obj The actor whose frame index to advance. Required.
|
|
* @param curSprite The sprite currently selected for the actor, whose frame
|
|
* count and loop flags decide what happens at the end.
|
|
* Required in practice, and **not** checked -- a `NULL` here is
|
|
* a crash, not an error.
|
|
* @param curtimems The current clock reading. Accepted for the hook's signature;
|
|
* this implementation does not use it, because the caller has
|
|
* already decided the frame is due.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems);
|
|
/**
|
|
* @brief The default `facefunc`: turn the actor to face whichever way it is moving.
|
|
*
|
|
* Clears every facing bit and sets the one matching the first movement bit it
|
|
* finds, in the order left, right, up, down -- so an actor moving diagonally
|
|
* faces the horizontal component. An actor with `movement_controls_face` clear
|
|
* is left alone entirely, which is the hook for something that aims
|
|
* independently of the way it walks.
|
|
*
|
|
* @param obj The actor to turn. Required.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
|
|
*
|
|
* @note An actor that has stopped moving is left with no facing bit at all,
|
|
* rather than keeping the way it was last facing -- so its state no longer
|
|
* matches any "standing still facing left" sprite. The implementation
|
|
* carries a TODO saying as much.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_automatic_face(akgl_Actor *obj);
|
|
/**
|
|
* @brief Attach one actor to another so it moves with it.
|
|
*
|
|
* A child is positioned relative to its parent rather than simulated: the
|
|
* physics step snaps it to the parent's position plus its own velocity fields,
|
|
* used here as a fixed offset. That is what a carried lantern or a turret on a
|
|
* tank wants.
|
|
*
|
|
* The parent takes a reference on the child, and releasing the parent releases
|
|
* every child with it.
|
|
*
|
|
* @param obj The parent. Required.
|
|
* @param child The actor to attach. Required. Must not already have a parent --
|
|
* this builds a tree, not a graph. Its `x`/`y` become an offset
|
|
* from the parent's position rather than a world position.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p child is `NULL`.
|
|
* @throws AKERR_RELATIONSHIP If @p child already has a parent. Detach it first --
|
|
* though note there is no detach function, so in practice this means
|
|
* releasing and rebuilding it.
|
|
* @throws AKERR_OUTOFBOUNDS If @p obj already has #AKGL_ACTOR_MAX_CHILDREN
|
|
* children.
|
|
*
|
|
* @warning Nothing checks for a cycle. Making an actor its own ancestor makes
|
|
* akgl_heap_release_actor recurse until the stack runs out.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_add_child(akgl_Actor *obj, akgl_Actor *child);
|
|
|
|
/*
|
|
* The control-map handler functions ("cmhf"). These are what
|
|
* akgl_controller_default binds the arrow keys and the D-pad to, and what a
|
|
* game's own akgl_Control bindings can point at. They are called with the
|
|
* control map's target actor, not with a global "player", so they work for any
|
|
* number of locally controlled actors.
|
|
*
|
|
* Each pair is symmetric: the `_on` handler starts movement in a direction and
|
|
* turns the actor to face it, and the `_off` handler stops it dead -- zeroing
|
|
* acceleration, thrust, environmental velocity and velocity on that axis, so
|
|
* there is no coasting. That is an arcade feel rather than a physical one, and
|
|
* it is deliberate; a game wanting momentum binds its own handlers instead.
|
|
*
|
|
* An `_on` handler clears *every* facing and movement bit before setting its
|
|
* own, so holding two directions at once moves in whichever was pressed last
|
|
* rather than diagonally.
|
|
*/
|
|
|
|
/**
|
|
* @brief Start the actor moving left, and turn it to face left.
|
|
* @param obj The actor to move, supplied by the control map. Required, along
|
|
* with its `basechar`, whose acceleration is negated onto `ax`.
|
|
* @param event The event that triggered this. Required, but not read -- the
|
|
* binding has already decided what the event means.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Stop the actor moving left, zeroing everything on the x axis.
|
|
* @param obj The actor to stop. Required. `basechar` is not needed here.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
|
* @note It clears the x axis whichever way the actor was going, so releasing
|
|
* left while holding right also stops the rightward movement.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Start the actor moving right, and turn it to face right.
|
|
* @param obj The actor to move. Required, along with its `basechar`.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Stop the actor moving right, zeroing everything on the x axis.
|
|
* @param obj The actor to stop. Required.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Start the actor moving up the screen, and turn it to face up.
|
|
* @param obj The actor to move. Required, along with its `basechar`, whose y
|
|
* acceleration is negated -- y grows downward.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Stop the actor moving up, zeroing everything on the y axis.
|
|
* @param obj The actor to stop. Required.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
|
* @note This also zeroes `ey`, so an actor under gravity has its accumulated
|
|
* fall cancelled by releasing a movement key.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Start the actor moving down the screen, and turn it to face down.
|
|
* @param obj The actor to move. Required, along with its `basechar`.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
|
|
/**
|
|
* @brief Stop the actor moving down, zeroing everything on the y axis.
|
|
* @param obj The actor to stop. Required.
|
|
* @param event The event that triggered this. Required, but not read.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
|
* @note Zeroes `ey` as well; see akgl_actor_cmhf_up_off.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
|
|
|
|
/**
|
|
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one registered actor.
|
|
*
|
|
* Runs the operations the iterator asks for, in a fixed order regardless of the
|
|
* order the bits were set: layer filter, update, tilemap scaling, render. With
|
|
* #AKGL_ITERATOR_OP_LAYERMASK set, an actor on any other layer is skipped
|
|
* entirely; with #AKGL_ITERATOR_OP_TILEMAPSCALE clear, `scale` is forced to 1.0
|
|
* rather than left alone.
|
|
*
|
|
* @param userdata The akgl_Iterator carrying the operation flags, passed through
|
|
* by `SDL_EnumerateProperties`. Required despite the `void *`.
|
|
* @param registry #AKGL_REGISTRY_ACTOR, supplied by SDL.
|
|
* @param name The actor's registry key. Required.
|
|
*
|
|
* @warning This is an SDL callback, so it returns `void` and has nowhere to
|
|
* propagate an error to. It ends in `FINISH_NORETURN`, which logs the
|
|
* stack trace and then calls libakerror's unhandled-error handler --
|
|
* whose default implementation **exits the process** with the error
|
|
* status. An actor whose `updatefunc` fails therefore terminates the
|
|
* game rather than skipping a frame. Install your own
|
|
* `akerr_handler_unhandled_error` if that is not what you want.
|
|
*/
|
|
void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, const char *name);
|
|
|
|
#endif // _AKGL_ACTOR_H_
|