Skip to main content

A 2D skeletal rigging, posing, and animation system

Project description

renpy-rigging

A 2D skeletal rigging, posing, and animation system for Ren'Py — including a full visual rig editor built in Ren'Py itself.

What is renpy-rigging?

  • A lightweight 2D cutout/skeletal animation runtime for Ren'Py
  • A visual rig & animation editor implemented as a Ren'Py project
  • A pip-installable Python package that Ren'Py games can use to load and render rigs, poses, and animations at runtime
  • A JSON-based, human-readable asset format for rigs, poses, animations, overlays, attachments, and scenes

The Guiding Principle

What you see in the editor is exactly what ships in your game.

No export pipelines. No mismatched math. No editor/runtime drift.

Features

Runtime Library (pip package)

  • Load character rigs from JSON
  • Load pose libraries and animation libraries
  • Render characters using:
    • Hierarchical joints (forward kinematics)
    • Per-part pivots
    • Per-part rotation, offset, scale
    • Z-order layering
  • Play animations:
    • Pose sequences with timing
    • Looping, one-shot, and ping-pong play modes
    • Easing functions (linear, ease_in, ease_out, ease_in_out)
    • Pose interpolation and blending
    • Multi-track composite animations with weighted additive blending
    • Variable playback speed
    • Per-frame horizontal/vertical flipping
  • Overlays: Layer clothing, accessories, and alternate art on top of (or replacing) existing parts — with slot-based auto-eviction for mutually exclusive items
  • Attachments: Attach fully-rigged independent objects (weapons, props) to character joints — with their own skeletons, poses, animations, and slot-based swapping
  • Scenes: Compose backgrounds, layered props, placed characters, and attachments into full environments with z-ordered layers and ambient sound
  • Sound: Three layers of audio — per-pose sounds, animation timeline SFX (up to 4 concurrent channels), and scene ambient audio
  • Outline/border rendering: Per-rig configurable borders and runtime-adjustable outline overlays with color, width, and alpha controls
  • Fully Ren'Py-native:
    • Uses Transform and displayables
    • No engine hacks or C extensions

Editor (RigLab)

  • Load a character rig and parts
  • Visualize:
    • Joints
    • Bone hierarchy
    • Part pivots
    • Draw order
  • Select and edit:
    • Joints (drag with mouse, nudge with arrow keys)
    • Parts (rotate, offset, adjust pivots)
  • Save:
    • Poses to poses.json
    • Animations to animations.json
    • Overlays to overlays.json
    • Attachments to attachments.json
  • Timeline editor:
    • Build animations from poses
    • Multi-track support with per-track weights and muting
    • Set timing and easing per step
    • Timeline sound placement
    • Per-frame overlay and attachment overrides
    • Preview loop, one-shot, or ping-pong playback
  • Export:
    • PNG frame export (transparent, solid-color, or scene background)
    • Spritesheet export (all animations tiled)
    • GIF export (pure-Python, no PIL required)
    • Cloud render API for GIF/MP4
  • Same renderer as the runtime library

Installation

Runtime Library

In your Ren'Py game's Python environment:

pip install renpy-rigging

Then in Ren'Py:

init python:
    import renpy_rigging

Editor (RigLab)

  1. Open the riglab/ folder in the Ren'Py Launcher
  2. Launch the project
  3. Select a sample character or add your own

Quick Start

Directory Layout

game/
  characters/
    vince/
      rig.json           # Joints, parts, canvas, border config
      poses.json         # Named poses (deltas from neutral)
      animations.json    # Named animations (frame sequences)
      overlays.json      # Named overlay sets (clothing, accessories)
      attachments.json   # Attachment bindings (weapons, props)
      parts/             # PNG images for each body part
      overlays/          # Overlay images per overlay set
      sounds/            # Sound files
  attachments/
    tommy-gun/
      rig.json           # Attachment rig (joints, parts, attachment config)
      poses.json
      animations.json
      parts/
  scenes/
    speakeasy/
      scene.json         # Background, layers, placed items/characters
      parts/             # Scene prop images

Loading a Rig

init python:
    from renpy_rigging import Rig, PoseLibrary, AnimationLibrary, RigRenderer

    rig = Rig.load("characters/vince/rig.json")
    poses = PoseLibrary.load("characters/vince/poses.json")
    anims = AnimationLibrary.load("characters/vince/animations.json")

    renderer = RigRenderer(rig, poses, anims)

Or load everything from a directory (including overlays and attachment bindings):

init python:
    from renpy_rigging import RigRenderer

    vince = RigRenderer.from_directory("characters/vince")

Showing a Character

# Show a static pose
show expression renderer.show_pose("neutral") as vince:
    xalign 0.5
    yalign 0.7

# Play an animation
show expression renderer.play_animation("idle", loop=True) as vince:
    xalign 0.5
    yalign 0.7

# Play an animation mirrored
show expression renderer.play_animation("walk", loop=True, flip_h=True) as vince

Using Overlays

# Add an overlay (slot-aware — evicts any overlay in the same slot)
renderer.add_overlay("broadway-brawlers-shirt")

# Remove an overlay
renderer.remove_overlay("broadway-brawlers-shirt")

# Query by slot
renderer.get_overlay_by_slot("shirt")

Using Attachments

# Attach a weapon (slot-aware — evicts any attachment in the same slot)
renderer.add_attachment("tommy-gun")

# Set the attachment's pose
renderer.set_attachment_pose("tommy-gun", "firing")

# Play the attachment's own animation
renderer.play_attachment_animation("tommy-gun", "fire", loop=True)

# Remove
renderer.remove_attachment("tommy-gun")

Setting Up Scenes

init python:
    from renpy_rigging import SceneManager, register_channels

    register_channels()
    scene_mgr = SceneManager(os.path.join(renpy.config.gamedir, "scenes"))
    scene_mgr.register_images()  # enables "scene street" syntax

Then in your script:

# Using the scene manager
$ scene_mgr.show("speakeasy")

# Or with the custom rigscene statement (if registered)
rigscene speakeasy with dissolve

Scenes automatically composite backgrounds, layered props, placed characters, and attachments at the correct z-orders, and start ambient sound if configured.

Game Project Integration

You can integrate renpy-rigging into your Ren'Py game without pip installing the package. Add this to your options.rpy:

init -100 python:
    import sys, os
    src_path = os.path.normpath(os.path.join(renpy.config.gamedir, "..", "..", "src"))
    sys.path.insert(0, src_path)

Then import and re-export into the Ren'Py store in a rig_runtime.rpy file:

init -10 python:
    from renpy_rigging import (
        Rig, Pose, PoseLibrary, AnimationLibrary,
        RigRenderer, SceneManager, register_channels,
    )
    register_channels()

This is the pattern used by both the RigLab editor and the RigLabDemo game.

Data Formats

rig.json

Defines the skeleton (joints) and visual parts:

{
  "canvas": [600, 900],

  "joints": {
    "root":   {"parent": null, "pos": [300, 700]},
    "pelvis": {"parent": "root", "pos": [0, 0]},
    "chest":  {"parent": "pelvis", "pos": [0, -200]}
  },

  "parts": {
    "torso": {
      "image": "parts/torso.png",
      "parent_joint": "pelvis",
      "pos": [0, 0],
      "pivot": [130, 135],
      "z": 10
    }
  },

  "border": {"enabled": true, "width": 3, "color": "#000000"},
  "slots": ["weapon"]
}

poses.json

Poses store only deltas from the neutral rig:

{
  "neutral": {
    "joints": {},
    "parts": {}
  },
  "wave": {
    "joints": {
      "shoulder_R": {"rot": -120},
      "elbow_R": {"rot": 90}
    },
    "parts": {
      "hand_R": {"visible": false}
    },
    "overlays": {"hand-pistol": true},
    "attachments": {
      "tommy-gun": {"pose": "firing", "visible": true}
    },
    "sound": "sounds/whoosh.wav"
  }
}

animations.json

Animations are sequences of poses with timing. Three formats are supported (all backward-compatible):

Simple (flat list):

{
  "idle": [
    {"pose": "neutral", "ms": 500},
    {"pose": "breathe_in", "ms": 800, "ease": "ease_in_out"},
    {"pose": "neutral", "ms": 500},
    {"pose": "breathe_out", "ms": 800, "ease": "ease_in_out"}
  ]
}

With timeline sounds:

{
  "fire": {
    "frames": [
      {"pose": "firing", "ms": 80},
      {"pose": "neutral", "ms": 80}
    ],
    "sounds": [
      {"path": "sounds/gunshot.wav", "start_ms": 0},
      {"path": "sounds/shell_casing.wav", "start_ms": 100}
    ]
  }
}

Multi-track composite:

{
  "walk_and_breathe": {
    "tracks": [
      {"name": "Walk", "frames": [...], "weight": 1.0},
      {"name": "Breathe", "frames": [...], "weight": 0.5, "muted": false}
    ],
    "sounds": [...]
  }
}

overlays.json

Overlays associate extra art with body parts:

{
  "broadway-brawlers-shirt": {
    "_slot": "shirt",
    "torso": {"path": "torso.png", "replace": true},
    "arm_upper_L": true,
    "arm_upper_R": {"z_delta": 2}
  }
}

A part set to true uses all defaults (same part name as the image filename, layered on top). The _slot field enables auto-eviction when activating a new overlay in the same slot.

attachments.json (on the character)

Defines which attachments a character can use and how they connect:

{
  "tommy-gun": {
    "path": "attachments/tommy-gun",
    "default_joint": "wrist_R",
    "pos": [-5.0, 29.0],
    "rot": 90.0,
    "slot": "weapon"
  }
}

Attachment rig.json

An attachment's own rig.json includes an attachment config block:

{
  "name": "tommy-gun",
  "canvas": [1051, 345],
  "joints": {"root": {"parent": null, "pos": [525, 172]}},
  "parts": {...},
  "attachment": {
    "is_attachment": true,
    "default_attach_joint": "wrist_R",
    "inherit_rotation": true,
    "inherit_scale": false,
    "z_offset": 10,
    "slot": "weapon"
  }
}

scene.json

Defines a composed environment with backgrounds, layers, and placed entities:

{
  "canvas": [1456, 816],
  "background": {
    "color": "#000000",
    "image": "parts/speakeasy.png",
    "image_offset": [0, 0],
    "image_scale": 1.0
  },
  "layers": {
    "background": {"z": -100},
    "foreground": {"z": 100}
  },
  "items": [
    {
      "name": "barrel",
      "image": "parts/barrel.png",
      "layer": "background",
      "pos": [300, 400],
      "scale": [1.5, 1.5],
      "z_offset": 5
    }
  ],
  "characters": [
    {
      "name": "Vince",
      "source": "Vince-New",
      "layer": "background",
      "pos": [728, 408],
      "scale": [0.4, 0.4]
    }
  ],
  "attachments": [
    {
      "name": "Tommy-Gun",
      "source": "Tommy-Gun",
      "layer": "background",
      "pos": [188, 578],
      "scale": [0.6, 0.6]
    }
  ],
  "sound": "sounds/jazz.ogg",
  "sound_loop": true
}

Repository Structure

renpy-rigging/
  README.md
  pyproject.toml
  LICENSE

  src/
    renpy_rigging/
      __init__.py
      rig.py            # Rig data structures (joints, parts, canvas)
      pose.py           # Pose application, blending, interpolation
      animation.py      # Animation playback (multi-track, sounds, easing)
      overlay.py        # Overlay system (clothing, accessories)
      attachment.py     # Attachment system (weapons, props)
      scene.py          # Scene composition (backgrounds, layers, entities)
      render.py         # Ren'Py displayable builders
      audio.py          # Audio channel registration and management
      math2d.py         # Vec2, Transform2D, easing functions
      io.py             # JSON load/save helpers

  riglab/               # The Ren'Py editor app
    game/
      script.rpy
      options.rpy
      gui.rpy
      screens.rpy
      riglab/
        core/
          rig_runtime.rpy
          rig_io.rpy
          rig_math.rpy
        editor/
          editor_screen.rpy
          editor_input.rpy
          timeline_screen.rpy
        data/
          samples/
        main_menu.rpy

  riglabdemo/           # Demo game project
    game/
      characters/       # Character rigs
      attachments/      # Attachment rigs
      scenes/           # Scene definitions

  docs/
    rig_format.md
    authoring_guide.md
    renpy_integration.md

Editor Keyboard Shortcuts

Key Action
J Toggle joint visibility
B Toggle bone visibility
P Toggle pivot visibility
Arrow keys Nudge selected joint (1px)
Shift + Arrow keys Nudge selected joint (10px)
Space Play/pause animation (in timeline)

Design Philosophy

  • FK-first: Forward kinematics only in v1
  • No sprite sheets: Everything is runtime-composed
  • Human-editable JSON: All data formats are readable and editable
  • Editor and runtime share the same renderer
  • Procedural-friendly: Built for blending, aiming, reacting, not just canned loops
  • Slot-based resource management: Overlays and attachments use named slots for clean swapping (e.g., only one "weapon" or "shirt" at a time)

Development Roadmap

  • v0.1 — Viewer: Load rig.json, render neutral pose
  • v0.2 — Poses: Load poses.json, apply pose deltas
  • v0.3 — Editor: Select + drag joints, rotate parts, save poses
  • v0.4 — Animation: Timeline editor, playback system, export
  • v0.5 — Overlays, attachments, scene rendering, composite animations with sound

Future

  • IK helpers
  • Symmetry tools
  • Onion skinning
  • Event markers in animations
  • Physics jiggle bones
  • Auto-z reordering helpers

Non-Goals

This is not a Spine replacement. This is not a general-purpose animation suite.

This is a Ren'Py-first, game-dev-first rigging system.

Who This Is For

  • Visual novel devs who want live characters
  • 2D game devs who want:
    • Aiming arms
    • Breathing idles
    • Reactive poses
    • Procedural animation
  • Artists who think in volumes and joints, not sprite sheets

Documentation

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

renpy_rigging-0.5.1.tar.gz (80.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

renpy_rigging-0.5.1-py3-none-any.whl (87.2 kB view details)

Uploaded Python 3

File details

Details for the file renpy_rigging-0.5.1.tar.gz.

File metadata

  • Download URL: renpy_rigging-0.5.1.tar.gz
  • Upload date:
  • Size: 80.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for renpy_rigging-0.5.1.tar.gz
Algorithm Hash digest
SHA256 ce2a05e2472ce1730043ffcebc620228492c1b27a8f16ebde81ebb330b6feaa5
MD5 474a3446033e42286074f42039b58d67
BLAKE2b-256 d51d58118e85ccad5d6b1b9ee99beec9c6010cfbed21501cad8104e8584422ce

See more details on using hashes here.

File details

Details for the file renpy_rigging-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: renpy_rigging-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 87.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for renpy_rigging-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0c886061b6e5cbe7b914663430b58f716b26a2b0c9ba7175f2bef9d43654f1cc
MD5 450a0b85557fd1a000ea6d93552e37a3
BLAKE2b-256 d870b40b631212df36e941161599a2181961d5119e4150b4ad13b11e4547bb93

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page