Skip to content

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

ObjectAnimator

Performance timeline-based object animation engine for SA-MP and open.mp.

Version C++17 Pawn Platform MIT License


ObjectAnimator Preview

Watch the ObjectAnimator demonstration on YouTube


Overview

ObjectAnimator is a C++ server plugin and Pawn library that extends the native object movement system of SA-MP 0.3.7 and open.mp.

Instead of relying on chained MoveObject calls, Pawn timers, and OnObjectMoved callbacks, ObjectAnimator provides a deterministic timeline engine capable of controlling object position, rotation, easing, delays, events, looping, playback direction, seeking, and playback speed.

Animations are evaluated using elapsed-time interpolation inside a centralized C++ update loop, allowing complex sequences to remain concise on the Pawn side while keeping runtime overhead low.


Features

  • Timeline-based animation sequences
  • Smooth position and rotation interpolation
  • 34 easing equations
  • Simultaneous 6-DOF position and rotation transforms
  • Absolute and relative movement
  • Shortest-path Euler rotation
  • Continuous multi-turn rotations
  • Timeline waits and custom events
  • Pause, resume, stop, reset, and seek
  • Dynamic playback speed
  • Infinite and bounded looping
  • Ping-pong playback
  • Reusable immutable animation templates
  • Global and player object support
  • Generational safe handles
  • Centralized C++ update loop
  • No heap allocations during normal animation ticks
  • Pawn callbacks for animation lifecycle and timeline events

Why ObjectAnimator?

The native SA-MP object movement API is intentionally simple.

Functions such as MoveObject and MovePlayerObject are useful for basic movement, but complex animations normally require additional state management in Pawn.

Common limitations include:

  • constant-speed movement;
  • limited easing control;
  • difficult synchronization between movement and rotation;
  • callback chaining for multi-step sequences;
  • Pawn timers for pauses and delayed actions;
  • manual state tracking;
  • no built-in seeking;
  • no playback speed control;
  • no native ping-pong playback;
  • difficult animation reuse across multiple objects.

ObjectAnimator moves this logic into a dedicated animation engine.

A complete sequence can be declared once and then controlled as a single timeline.


Compatibility

Target Support
SA-MP 0.3.7 R1–R5 Supported
open.mp Supported through legacy plugin compatibility
Global Objects Supported
Player Objects Supported
Windows x86 Supported
Linux x86 Supported
Windows x64 Planned
Linux x64 Planned
Official Pawn Compiler Supported
Zeex Pawn Compiler Supported

ObjectAnimator operates entirely on the server. Players do not need to install any client-side modification.


Installation

Download the appropriate archive from the Releases page.

Windows

Extract:

ObjectAnimator.dll

into:

plugins/

Then place:

object_animator.inc

inside:

pawno/include/

Add the plugin to server.cfg:

plugins ObjectAnimator.dll

Linux

Extract:

ObjectAnimator.so

into:

plugins/

Place:

object_animator.inc

inside:

pawno/include/

Then add:

plugins ObjectAnimator.so

to server.cfg.

For additional information, see docs/installation.md.


Quick Start

Include ObjectAnimator after your SA-MP or open.mp includes:

#include <a_samp>
#include <object_animator>

Create an object and attach an animator:

new Animator:gGateAnim;

public OnGameModeInit()
{
    new gate = CreateObject(
        980,
        1544.5, -1630.8, 13.0,
        0.0, 0.0, 90.0
    );

    gGateAnim = OA_Create(gate);

    OA_MoveBy(
        gGateAnim,
        0.0, 0.0, 5.0,
        1500,
        EASE_OUT_CUBIC
    );

    OA_Wait(gGateAnim, 2000);

    OA_MoveBy(
        gGateAnim,
        0.0, 0.0, -5.0,
        1500,
        EASE_IN_CUBIC
    );

    OA_Play(gGateAnim);

    return 1;
}

public OnGameModeExit()
{
    if (OA_IsValid(gGateAnim))
    {
        OA_Destroy(gGateAnim);
    }

    return 1;
}

The resulting timeline is:

0 ms                 1500 ms              3500 ms              5000 ms
 │                       │                    │                     │
 ├──── Move Up ──────────┤────── Wait ──────┤──── Move Down ──────┤
 │   EASE_OUT_CUBIC      │      2000 ms      │   EASE_IN_CUBIC     │

No Pawn timers or movement callback chains are required.


Timeline Engine

An animator owns an ordered sequence of timeline nodes.

Each node begins immediately after the previous node finishes.

For example:

OA_MoveBy(anim, 0.0, 0.0, 5.0, 1000, EASE_OUT_CUBIC);
OA_Wait(anim, 2000);
OA_RotateBy(anim, 0.0, 0.0, 90.0, 750, EASE_IN_OUT_QUAD);
OA_Event(anim, "finished_rotation");

Conceptually:

Timeline
│
├── MoveBy
│   └── 1000 ms
│
├── Wait
│   └── 2000 ms
│
├── RotateBy
│   └── 750 ms
│
└── Event
    └── 0 ms

Total duration:

1000 + 2000 + 750 = 3750 ms

Instant nodes such as SetPosition, SetRotation, and Event consume no timeline duration.

See docs/timeline.md for details.


Movement

Absolute Movement

Move an object toward a fixed world-space coordinate:

OA_MoveTo(
    anim,
    1500.0,
    -1700.0,
    20.0,
    2000,
    EASE_IN_OUT_CUBIC
);

Relative Movement

Move relative to the position produced by the previous timeline node:

OA_MoveBy(
    anim,
    0.0,
    0.0,
    5.0,
    1000,
    EASE_OUT_CUBIC
);

Relative nodes are resolved deterministically from the timeline state rather than depending on when the node happens to execute.

This allows seeking and replaying to produce consistent results.


Rotation

Absolute Rotation

OA_RotateTo(
    anim,
    0.0,
    0.0,
    180.0,
    1000,
    EASE_IN_OUT_QUAD
);

RotateTo uses shortest-path angular interpolation.

For example:

350° → 10°

is interpreted as:

+20°

instead of:

-340°

This prevents unnecessary full rotations when interpolating Euler angles.


Relative Rotation

OA_RotateBy(
    anim,
    0.0,
    0.0,
    720.0,
    3000,
    EASE_LINEAR
);

Unlike shortest-path absolute rotation, relative rotations preserve the requested angular delta.

Therefore:

720°

produces two complete rotations.

Similarly:

1080°

produces three complete rotations.


Transform Animation

Position and rotation can be interpolated simultaneously.

Absolute Transform

OA_TransformTo(
    anim,

    1500.0,
    -1700.0,
    20.0,

    0.0,
    0.0,
    180.0,

    2000,
    EASE_IN_OUT_CUBIC
);

Relative Transform

OA_TransformBy(
    anim,

    5.0,
    0.0,
    2.0,

    0.0,
    0.0,
    90.0,

    1500,
    EASE_OUT_QUAD
);

Both position and rotation use the same normalized timeline progress.


Easing

ObjectAnimator provides 34 easing equations based on the commonly used Robert Penner easing families.

Linear

EASE_LINEAR

Quadratic

EASE_IN_QUAD
EASE_OUT_QUAD
EASE_IN_OUT_QUAD

Cubic

EASE_IN_CUBIC
EASE_OUT_CUBIC
EASE_IN_OUT_CUBIC

Quartic

EASE_IN_QUART
EASE_OUT_QUART
EASE_IN_OUT_QUART

Quintic

EASE_IN_QUINT
EASE_OUT_QUINT
EASE_IN_OUT_QUINT

Sine

EASE_IN_SINE
EASE_OUT_SINE
EASE_IN_OUT_SINE

Exponential

EASE_IN_EXPO
EASE_OUT_EXPO
EASE_IN_OUT_EXPO

Circular

EASE_IN_CIRC
EASE_OUT_CIRC
EASE_IN_OUT_CIRC

Back

EASE_IN_BACK
EASE_OUT_BACK
EASE_IN_OUT_BACK

Elastic

EASE_IN_ELASTIC
EASE_OUT_ELASTIC
EASE_IN_OUT_ELASTIC

Bounce

EASE_IN_BOUNCE
EASE_OUT_BOUNCE
EASE_IN_OUT_BOUNCE

Refer to docs/easing.md for formulas, graphs, and recommendations.


Playback Control

ObjectAnimator provides independent runtime control over every animator.

Play

OA_Play(anim);

Pause

OA_Pause(anim);

Resume

OA_Resume(anim);

Stop

OA_Stop(anim);

Reset

OA_Reset(anim);

State

if (OA_IsPlaying(anim))
{
    // Animator is running.
}

if (OA_IsPaused(anim))
{
    // Animator is paused.
}

Playback Speed

Animation speed can be modified while the animator is running.

OA_SetSpeed(anim, 2.0);

Playback now runs at twice the normal rate.

OA_SetSpeed(anim, 0.5);

Playback now runs at half speed.

Retrieve the current rate with:

new Float:speed = OA_GetSpeed(anim);

Seeking

An animator can jump directly to an exact timeline position.

OA_Seek(anim, 1500);

ObjectAnimator evaluates the timeline at 1500 ms and immediately applies the calculated transform.

Timeline information can also be queried:

new current = OA_GetTime(anim);
new duration = OA_GetDuration(anim);

This allows systems such as:

  • synchronized machinery;
  • animation debugging;
  • timeline scrubbing;
  • progress indicators;
  • state restoration;
  • scripted cinematics.

Looping

Enable looping:

OA_SetLoop(anim, true);

Infinite looping:

OA_SetLoopCount(anim, 0);

Bounded looping:

OA_SetLoopCount(anim, 3);

The animation runs three iterations before finishing.


Ping-Pong Playback

Ping-pong playback reverses direction each time the timeline reaches an endpoint.

OA_SetPingPong(anim, true);
OA_Play(anim);

Playback becomes:

START ──────────────────► END
      ◄──────────────────
START ──────────────────► END

This is useful for:

  • moving platforms;
  • pistons;
  • industrial machinery;
  • barriers;
  • environmental motion;
  • repetitive mechanical systems.

Reusable Animation Templates

Animations that share the same timeline can be stored as reusable templates.

new Animation:gDoorRoutine;

gDoorRoutine = OA_AnimationCreate();

OA_AnimationMoveBy(
    gDoorRoutine,
    0.0,
    0.0,
    3.0,
    1000,
    EASE_OUT_CUBIC
);

OA_AnimationWait(
    gDoorRoutine,
    2000
);

OA_AnimationMoveBy(
    gDoorRoutine,
    0.0,
    0.0,
    -3.0,
    1000,
    EASE_IN_CUBIC
);

The same animation can then be applied to multiple animators:

OA_ApplyAnimation(anim1, gDoorRoutine);
OA_ApplyAnimation(anim2, gDoorRoutine);
OA_ApplyAnimation(anim3, gDoorRoutine);

Each animator receives an independent playback state while referencing the reusable timeline definition.

This is particularly useful for large systems containing many objects that share identical movement behavior.

See docs/reusable-animations.md.


Callbacks

ObjectAnimator exposes lifecycle and timeline callbacks to Pawn.

forward OnOAStart(Animator:animator);
forward OnOAPause(Animator:animator);
forward OnOAResume(Animator:animator);
forward OnOAStop(Animator:animator);
forward OnOAFinish(Animator:animator);

forward OnOALoop(
    Animator:animator,
    loop_index
);

forward OnOANodeStart(
    Animator:animator,
    node_index
);

forward OnOANodeFinish(
    Animator:animator,
    node_index
);

forward OnOAEvent(
    Animator:animator,
    const event[]
);

Example:

public OnOAEvent(Animator:animator, const event[])
{
    if (!strcmp(event, "elevator_arrived"))
    {
        SendClientMessageToAll(
            -1,
            "The elevator has arrived."
        );
    }

    return 1;
}

More information is available in docs/callbacks.md.


API Reference

Lifecycle

OA_Create
OA_CreatePlayerObject
OA_Destroy
OA_IsValid

Timeline Building

OA_MoveTo
OA_MoveBy

OA_RotateTo
OA_RotateBy

OA_TransformTo
OA_TransformBy

OA_SetPosition
OA_SetRotation

OA_Wait
OA_Event

Playback

OA_Play
OA_Pause
OA_Resume
OA_Stop
OA_Reset

OA_IsPlaying
OA_IsPaused

Timeline Control

OA_SetLoop
OA_SetLoopCount
OA_SetPingPong

OA_SetSpeed
OA_GetSpeed

OA_Seek
OA_GetTime
OA_GetDuration

Animation Templates

OA_AnimationCreate
OA_AnimationDestroy
OA_AnimationClear

OA_AnimationMoveTo
OA_AnimationMoveBy

OA_AnimationRotateTo
OA_AnimationRotateBy

OA_AnimationTransformTo
OA_AnimationTransformBy

OA_AnimationWait
OA_AnimationEvent

OA_ApplyAnimation

Diagnostics

OA_SetDebug
OA_GetLastError

The complete native reference, including parameters, return values, and examples, is available in docs/api-reference.md.


Handle Safety

ObjectAnimator uses generational handles rather than directly exposing internal pointers or array indexes.

A handle identifies:

┌──────────────────────────────┐
│         Animator Handle      │
├──────────────┬───────────────┤
│ Generation   │ Slot          │
└──────────────┴───────────────┘

When an animator is destroyed, its generation changes before its slot can be reused.

A stale handle therefore cannot accidentally reference a newly created animator occupying the same internal slot.

Example:

new Animator:anim = OA_Create(objectid);

OA_Destroy(anim);

if (!OA_IsValid(anim))
{
    // The stale handle is safely rejected.
}

Error Handling

Invalid input is rejected without intentionally dereferencing invalid animator handles.

The most recent error can be retrieved using:

new error = OA_GetLastError();

Available error codes:

Code Name Description
0 OA_ERROR_NONE No error
1 OA_ERROR_INVALID_HANDLE Animator handle is invalid or stale
2 OA_ERROR_OBJECT_NOT_FOUND Target object does not exist
3 OA_ERROR_EMPTY_TIMELINE Playback requested without timeline nodes
4 OA_ERROR_ALREADY_PLAYING Animator is already running
5 OA_ERROR_NOT_PLAYING Operation requires active playback
6 OA_ERROR_INVALID_PARAMETER One or more parameters are invalid
7 OA_ERROR_MAX_CAPACITY Animator capacity has been reached

Performance

ObjectAnimator uses a centralized update loop and only processes active animators.

Runtime complexity is approximately:

O(active animators)

rather than:

O(all allocated animators)

Normal animation ticks perform no heap allocation.

The following benchmark results were collected using tests/benchmarks.cpp over 1,000 consecutive engine ticks on the documented Linux x86 test environment.

Active Animators Mean / Tick P95 Approx. 20 ms Tick Budget
100 8.94 µs 6.48 µs 0.045%
500 27.88 µs 31.42 µs 0.139%
1,000 46.58 µs 61.56 µs 0.233%

Benchmark results depend on processor, compiler, optimization flags, operating system, timeline complexity, and plugin configuration. They should not be interpreted as universal performance guarantees.

Memory usage scales primarily with:

allocated animators + timeline nodes + animation templates

Detailed benchmark methodology is available in docs/performance.md.


Architecture

At a high level:

Pawn Script
    │
    │ OA_* natives
    ▼
┌─────────────────────────┐
│     ObjectAnimator       │
│      Native Layer       │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│     Animator Manager     │
│                         │
│  Generational Handles   │
│  Active Animator List   │
│  Playback State         │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│      Timeline Engine     │
│                         │
│  Nodes                  │
│  Easing                 │
│  Seeking                │
│  Looping                │
│  Ping-Pong              │
└────────────┬────────────┘
             │
             ▼
┌─────────────────────────┐
│ SA-MP / open.mp Objects │
└─────────────────────────┘

The Pawn API handles configuration while interpolation and playback state are maintained by the C++ engine.


Examples

Ready-to-compile examples are available under examples/.

basic_gate.pwn

Demonstrates:

  • relative movement;
  • easing;
  • timeline waits;
  • completion callbacks.

elevator.pwn

Demonstrates:

  • multi-stage movement;
  • acceleration and deceleration;
  • timeline events;
  • arrival handling.

rotating_platform.pwn

Demonstrates:

  • looping;
  • continuous rotation;
  • synchronized movement and rotation;
  • relative transforms.

complex_machine.pwn

Demonstrates:

  • reusable animation templates;
  • multiple independent animators;
  • complex timeline sequencing;
  • event-driven machinery.

Building From Source

Requirements

  • CMake 3.16 or newer
  • C++17-compatible compiler
  • SA-MP plugin SDK / compatible plugin interface
  • 32-bit build toolchain for SA-MP

Linux

Install the required multilib toolchain for your distribution.

Configure:

cmake -B build \
    -DCMAKE_BUILD_TYPE=Release

Build:

cmake --build build

The resulting plugin is generated as:

build/ObjectAnimator.so

Windows

ObjectAnimator can be cross-compiled using MinGW-w64.

cmake -B build-windows \
    -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw-x86.cmake \
    -DCMAKE_BUILD_TYPE=Release

Build:

cmake --build build-windows

Output:

build-windows/ObjectAnimator.dll

Release Packaging

Release archives can be generated through CMake targets.

Linux

cmake --build build --target package-linux

Output:

release/ObjectAnimator-linux-x86.tar.gz

Windows

cmake --build build --target package-windows

Output:

release/ObjectAnimator-windows-x86.zip

A release package should contain at minimum:

ObjectAnimator/
├── plugins/
│   └── ObjectAnimator.dll / ObjectAnimator.so
│
├── pawno/
│   └── include/
│       └── object_animator.inc
│
├── examples/
│   ├── basic_gate.pwn
│   ├── elevator.pwn
│   ├── rotating_platform.pwn
│   └── complex_machine.pwn
│
├── README.md
└── LICENSE

Testing

Run the unit test suite:

./build/object_animator_tests

Run the benchmark suite:

./build/object_animator_benchmarks

Contributions should pass the relevant tests before being submitted.


Documentation

Additional documentation is available under docs/.

Document Description
installation.md Installation and server configuration
api-reference.md Complete Pawn native reference
timeline.md Timeline behavior and node evaluation
easing.md Easing equations and usage
callbacks.md Callback reference
reusable-animations.md Animation template system
performance.md Benchmarks and methodology
troubleshooting.md Common installation and runtime issues

Known Limitations

  • Rotation uses Euler angles to remain compatible with the SA-MP object API.
  • Object visibility remains subject to SA-MP/open.mp streaming behavior.
  • Network update smoothness ultimately depends on the underlying server/client object synchronization.
  • ObjectAnimator does not modify the game client.
  • Native 64-bit plugin builds are not currently part of the standard release targets.

Roadmap

Possible future extensions include:

  • vehicle animation adapter;
  • actor animation adapter;
  • interpolated camera tracks;
  • additional timeline composition utilities;
  • native open.mp 64-bit builds;
  • expanded profiling and diagnostics.

Potential roadmap items are not guaranteed until implemented and released.


Contributing

Contributions, bug reports, and pull requests are welcome.

When submitting code:

  1. Keep changes focused and documented.
  2. Preserve backward compatibility where practical.
  3. Add or update tests for behavioral changes.
  4. Run the unit test suite.
  5. Run relevant benchmarks for performance-sensitive changes.
  6. Avoid introducing allocations into the animation tick path without justification.

License

ObjectAnimator is released under the MIT License.

Copyright © ObjectAnimator contributors.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages