Forest Logo
search
package_2

smartmotion

By @alqw

Roblox

Mirrored

SmartMotion

Root motion + bone-driven animation system для Roblox. Решает проблему скинд-меш ригов (Mixamo и подобных), где стандартный Animator не двигает кости, плюс даёт честный root motion без двойного счёта.

TL;DR: позволяет анимациям визуально работать как в Animation Editor preview (включая сальто, стойки на руках, body twists) И при этом физически двигать персонажа по миру (как root motion в Unity / Unreal). Коллизия следует за телом.


Установка

Через Wally — добавь в wally.toml своего проекта:

[dependencies]
SmartMotion = "alqw/smartmotion@0.1.0"
wally install

Пакет окажется в ReplicatedStorage.Packages.SmartMotion:

local SmartMotion = require(ReplicatedStorage.Packages.SmartMotion)

Полный гайд по интеграции в реальную игру (server + client + RemoteEvent) — docs/TUTORIAL.md.


Зачем это нужно

Проблема 1: Стандартный Animator + скинд-меш = тело не анимируется

При импорте Mixamo character как skinned mesh:

  • HumanoidRootPart = MeshPart (всё тело — один skinned mesh)
  • Иерархия Bone инстансов внутри MeshPart

Когда Animator:LoadAnimation(anim):Play():

  • ✓ Track запускается, IsPlaying = true
  • Bone.Transform остаётся identity — animator не записывает в bones
  • Track.TimePosition остаётся 0
  • ✗ Тело визуально не двигается

SmartMotion обходит: BoneDrive mode читает pose data из KeyframeSequence сам и каждый кадр устанавливает Bone.Transform вручную.

Проблема 2: Root motion в Roblox из коробки нет

Анимации Roblox это "in-place" — двигают кости относительно HRP, но сам HRP не двигается. Чтобы character физически перемещался — нужен отдельный механизм.

SmartMotion обходит: RootMotion mode запекает позиционные смещения mixamorig:Hips в маркеры (Keyframe с именами SM;x,y,z,...), и runtime каждый кадр выставляет HRP.CFrame согласно интерполированному маркеру.

Проблема 3: Двойной счёт при комбинации BoneDrive + RootMotion

Если оба активны одновременно:

  • BoneDrive: Hips bone Transform = pose (lift +5.6 Y)
  • RootMotion: HRP CFrame += pose (lift +5.6 Y)
  • Визуально Hips world = HRP + bone = +11.2 Y (DOUBLE)

SmartMotion обходит: RootBone setting в BoneDrive — для указанной кости применяется только rotation (translation handled by HRP RootMotion). Default "mixamorig:Hips" для Mixamo.

Проблема 4: Bone.Transform не реплицируется server→client

Если запустить BoneDrive на сервере — клиенты видят T-pose.

SmartMotion обходит: BoneDrive обязан запускаться в LocalScript. HRP motion наоборот — на сервере (CFrame реплицируется server→client).

Проблема 5: HRP.CFrame не реплицируется client→server для NPC

Если двигать HRP NPC из клиента — сервер не примет (server-owned).

SmartMotion обходит: с AnchorRoot = true сервер анкорит HRP, что bypass'ит network ownership — все клиенты получают CFrame обновления.


Архитектура

┌──────────────────────────────────┐                      ┌──────────────────────────────────┐
│  CLIENT                          │                      │  SERVER                          │
│  StarterPlayerScripts.LocalScript│                      │  ServerScriptService.            │
│                                  │                      │  SmartMotionRunner               │
│  • Ловит ввод (E)                │   RemoteEvent        │                                  │
│  • Шлёт сигнал серверу           │ ──FireServer(mode)─> │  • Принимает сигнал              │
│                                  │                      │  • Запускает HRP root motion     │
│                                  │ <─FireAllClients()── │  • Шлёт "start"/"stop" клиентам  │
│  • На "start": BoneDrive         │                      │                                  │
│    запускает кости локально      │                      │  Mode = "RootMotion"             │
│    Skipping Hips translation     │                      │  AnchorRoot = true               │
│    (keeps Hips rotation)         │                      │  RotationMode = "None"           │
│                                  │                      │                                  │
│  Mode = "BoneDrive"              │                      │                                  │
│  RootBone = "mixamorig:Hips"     │                      │                                  │
└──────────────────────────────────┘                      └──────────────────────────────────┘
        │                                                          │
        │  Bone.Transform                                          │  HumanoidRootPart.CFrame
        │  (локально, не реплицируется)                            │  (реплицируется server→client)
        ▼                                                          ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │                       observer (NPC Model)                      │
   │                                                                 │
   │   HumanoidRootPart [MeshPart] ←── server двигает CFrame         │
   │      └─ mixamorig:Hips [Bone] ←── client держит at identity     │
   │                                    translation (но крутит rot)  │
   │            └─ mixamorig:Spine [Bone]                            │
   │                  └─ ... 54 костей ←── каждый клиент крутит      │
   │                                       Bone.Transform у себя     │
   └─────────────────────────────────────────────────────────────────┘

Почему такой split

Математически эквивалентны два подхода:

ПодходBonesHRPVisualCollision
A (used)rotation only at roottranslates per bakeidentical to Anim Editorfollows body
Bfull posestaticidentical to Anim Editorstays at start

Оба дают тот же визуал (доказывается компонентным разложением CFrame). Подход A выбран потому что коллизия HRP следует за телом — важно для прыжков, лазания, чтобы capsule оказалась там же где body визуально.


Файлы

Структура репозитория:

Путь в репоНазначение
src/init.luauModuleScript SmartMotion — public API (façade)
src/Player.luauMotion loops: RootMotion + BoneDrive
src/RigAdapter.luauАбстракция над ригами (R15 / R6 / Mixamo / custom)
src/MarkerCodec.luauEncode/decode маркеров root motion
examples/server/SmartMotionRunner.server.luauПример: server HRP motion + warmup
examples/client/SmartMotionController.client.luauПример: client BoneDrive + ввод + warmup
plugin/SmartMotionBaker.server.luauStudio-плагин: бейкер маркеров
docs/TUTORIAL · WORKFLOW · REFERENCE · INDEX

В рантайме (после wally install + Rojo):

ИнстансКлассНазначение
ReplicatedStorage.Packages.SmartMotionModuleScriptPublic API (shared)
ReplicatedStorage.SmartMotionToggleRemoteEventClient↔Server сигнал
ServerScriptService.*ScriptServer HRP motion (translation)
StarterPlayer.StarterPlayerScripts.*LocalScriptClient BoneDrive (визуал) + ввод
%LOCALAPPDATA%\Roblox\Plugins\SmartMotionBaker.rbxmLocalPluginБейкер маркеров

Quick start

  1. Подключи пакет (Wally) + скрипты из examples/ в плейс со скинд-меш NPC observer (подробно — docs/TUTORIAL.md).
  2. F5 (Play Solo).
  3. В Output должно появиться:
    [Server] SmartMotion warmup complete
    [Client] SmartMotion warmup complete (hidden)
    [Server] SmartMotionRunner ready. E = full root motion, Q = in-place only
    
  4. Character стоит в T-pose (warmup произошёл невидимо).
  5. E → full root motion: HRP едет, body анимируется, всё точно как в Animation Editor.
  6. Q → in-place: HRP стоит, body танцует на месте.

Следующее

  • Подключить в свою игру — установка через Wally + server/client/RemoteEvent
  • Добавить новую анимацию — полный шаг-за-шагом гайд
  • API reference + опции — все settings, методы, форматы
  • Troubleshooting — типичные баги
  • Установка плагина — Studio Local Plugin
  • Вся документация

Package Details

Install command (Click to copy)


Version

0.1.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.