{"id":"alqw/smartmotion","name":"smartmotion","scope":"alqw","platform":"roblox","description":"Root motion + bone-driven animation for skinned-mesh (Mixamo) rigs in Roblox","version":"0.1.0","latest":"0.1.0","versions":["0.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":false,"dependencies":{},"integrity":"0ee58f0a4944d76001e7e57e5fd16f7671adbd27b99b268d654b84eea42d44a4","likes":0,"downloads":0,"install":"forest install alqw/smartmotion","url":"https://forest.dev/p/roblox/alqw/smartmotion","files":"https://api.forest.dev/ai/package/roblox/alqw/smartmotion/files","readme":"# SmartMotion\n\nRoot motion + bone-driven animation system для Roblox. Решает проблему\nскинд-меш ригов (Mixamo и подобных), где стандартный Animator не двигает кости,\nплюс даёт честный root motion без двойного счёта.\n\n> **TL;DR**: позволяет анимациям визуально работать как в Animation Editor preview\n> (включая сальто, стойки на руках, body twists) И при этом физически двигать\n> персонажа по миру (как root motion в Unity / Unreal). Коллизия следует за телом.\n\n---\n\n## Установка\n\nЧерез [Wally](https://wally.run) — добавь в `wally.toml` своего проекта:\n\n```toml\n[dependencies]\nSmartMotion = \"alqw/smartmotion@0.1.0\"\n```\n\n```sh\nwally install\n```\n\nПакет окажется в `ReplicatedStorage.Packages.SmartMotion`:\n\n```lua\nlocal SmartMotion = require(ReplicatedStorage.Packages.SmartMotion)\n```\n\nПолный гайд по интеграции в реальную игру (server + client + RemoteEvent) —\n**[docs/TUTORIAL.md](docs/TUTORIAL.md)**.\n\n---\n\n## Зачем это нужно\n\n### Проблема 1: Стандартный Animator + скинд-меш = тело не анимируется\n\nПри импорте Mixamo character как skinned mesh:\n- `HumanoidRootPart` = `MeshPart` (всё тело — один skinned mesh)\n- Иерархия `Bone` инстансов внутри MeshPart\n\nКогда `Animator:LoadAnimation(anim):Play()`:\n- ✓ Track запускается, `IsPlaying = true`\n- ✗ `Bone.Transform` **остаётся identity** — animator не записывает в bones\n- ✗ `Track.TimePosition` **остаётся 0**\n- ✗ Тело визуально не двигается\n\n**SmartMotion обходит**: `BoneDrive` mode читает pose data из `KeyframeSequence`\nсам и каждый кадр устанавливает `Bone.Transform` вручную.\n\n### Проблема 2: Root motion в Roblox из коробки нет\n\nАнимации Roblox это \"in-place\" — двигают кости относительно HRP, но сам HRP\nне двигается. Чтобы character физически перемещался — нужен отдельный механизм.\n\n**SmartMotion обходит**: `RootMotion` mode запекает позиционные смещения\n`mixamorig:Hips` в маркеры (`Keyframe` с именами `SM;x,y,z,...`), и runtime\nкаждый кадр выставляет `HRP.CFrame` согласно интерполированному маркеру.\n\n### Проблема 3: Двойной счёт при комбинации BoneDrive + RootMotion\n\nЕсли оба активны одновременно:\n- BoneDrive: Hips bone Transform = pose (lift +5.6 Y)\n- RootMotion: HRP CFrame += pose (lift +5.6 Y)\n- Визуально Hips world = HRP + bone = **+11.2 Y (DOUBLE)**\n\n**SmartMotion обходит**: `RootBone` setting в BoneDrive — для указанной кости\nприменяется **только rotation** (translation handled by HRP RootMotion).\nDefault `\"mixamorig:Hips\"` для Mixamo.\n\n### Проблема 4: Bone.Transform не реплицируется server→client\n\nЕсли запустить BoneDrive на сервере — клиенты видят T-pose.\n\n**SmartMotion обходит**: BoneDrive **обязан** запускаться в LocalScript.\nHRP motion наоборот — на сервере (CFrame реплицируется server→client).\n\n### Проблема 5: HRP.CFrame не реплицируется client→server для NPC\n\nЕсли двигать HRP NPC из клиента — сервер не примет (server-owned).\n\n**SmartMotion обходит**: с `AnchorRoot = true` сервер анкорит HRP, что\nbypass'ит network ownership — все клиенты получают CFrame обновления.\n\n---\n\n## Архитектура\n\n```\n┌──────────────────────────────────┐                      ┌──────────────────────────────────┐\n│  CLIENT                          │                      │  SERVER                          │\n│  StarterPlayerScripts.LocalScript│                      │  ServerScriptService.            │\n│                                  │                      │  SmartMotionRunner               │\n│  • Ловит ввод (E)                │   RemoteEvent        │                                  │\n│  • Шлёт сигнал серверу           │ ──FireServer(mode)─> │  • Принимает сигнал              │\n│                                  │                      │  • Запускает HRP root motion     │\n│                                  │ <─FireAllClients()── │  • Шлёт \"start\"/\"stop\" клиентам  │\n│  • На \"start\": BoneDrive         │                      │                                  │\n│    запускает кости локально      │                      │  Mode = \"RootMotion\"             │\n│    Skipping Hips translation     │                      │  AnchorRoot = true               │\n│    (keeps Hips rotation)         │                      │  RotationMode = \"None\"           │\n│                                  │                      │                                  │\n│  Mode = \"BoneDrive\"              │                      │                                  │\n│  RootBone = \"mixamorig:Hips\"     │                      │                                  │\n└──────────────────────────────────┘                      └──────────────────────────────────┘\n        │                                                          │\n        │  Bone.Transform                                          │  HumanoidRootPart.CFrame\n        │  (локально, не реплицируется)                            │  (реплицируется server→client)\n        ▼                                                          ▼\n   ┌─────────────────────────────────────────────────────────────────┐\n   │                       observer (NPC Model)                      │\n   │                                                                 │\n   │   HumanoidRootPart [MeshPart] ←── server двигает CFrame         │\n   │      └─ mixamorig:Hips [Bone] ←── client держит at identity     │\n   │                                    translation (но крутит rot)  │\n   │            └─ mixamorig:Spine [Bone]                            │\n   │                  └─ ... 54 костей ←── каждый клиент крутит      │\n   │                                       Bone.Transform у себя     │\n   └─────────────────────────────────────────────────────────────────┘\n```\n\n### Почему такой split\n\nМатематически эквивалентны два подхода:\n\n| Подход | Bones | HRP | Visual | Collision |\n|---|---|---|---|---|\n| A (used) | rotation only at root | translates per bake | identical to Anim Editor | follows body |\n| B | full pose | static | identical to Anim Editor | stays at start |\n\nОба дают тот же визуал (доказывается компонентным разложением CFrame). Подход A\nвыбран потому что **коллизия HRP следует за телом** — важно для прыжков,\nлазания, чтобы capsule оказалась там же где body визуально.\n\n---\n\n## Файлы\n\nСтруктура репозитория:\n\n| Путь в репо | Назначение |\n|---|---|\n| `src/init.luau` | ModuleScript `SmartMotion` — public API (façade) |\n| `src/Player.luau` | Motion loops: RootMotion + BoneDrive |\n| `src/RigAdapter.luau` | Абстракция над ригами (R15 / R6 / Mixamo / custom) |\n| `src/MarkerCodec.luau` | Encode/decode маркеров root motion |\n| `examples/server/SmartMotionRunner.server.luau` | Пример: server HRP motion + warmup |\n| `examples/client/SmartMotionController.client.luau` | Пример: client BoneDrive + ввод + warmup |\n| `plugin/SmartMotionBaker.server.luau` | Studio-плагин: бейкер маркеров |\n| `docs/` | [TUTORIAL](docs/TUTORIAL.md) · [WORKFLOW](docs/WORKFLOW.md) · [REFERENCE](docs/REFERENCE.md) · [INDEX](docs/INDEX.md) |\n\nВ рантайме (после `wally install` + Rojo):\n\n| Инстанс | Класс | Назначение |\n|---|---|---|\n| `ReplicatedStorage.Packages.SmartMotion` | ModuleScript | Public API (shared) |\n| `ReplicatedStorage.SmartMotionToggle` | RemoteEvent | Client↔Server сигнал |\n| `ServerScriptService.*` | Script | Server HRP motion (translation) |\n| `StarterPlayer.StarterPlayerScripts.*` | LocalScript | Client BoneDrive (визуал) + ввод |\n| `%LOCALAPPDATA%\\Roblox\\Plugins\\SmartMotionBaker.rbxm` | LocalPlugin | Бейкер маркеров |\n\n---\n\n## Quick start\n\n1. Подключи пакет (Wally) + скрипты из [examples/](examples/) в плейс со скинд-меш NPC `observer` (подробно — [docs/TUTORIAL.md](docs/TUTORIAL.md)).\n2. F5 (Play Solo).\n3. В Output должно появиться:\n   ```\n   [Server] SmartMotion warmup complete\n   [Client] SmartMotion warmup complete (hidden)\n   [Server] SmartMotionRunner ready. E = full root motion, Q = in-place only\n   ```\n4. Character стоит в T-pose (warmup произошёл невидимо).\n5. **E** → full root motion: HRP едет, body анимируется, всё точно как в Animation Editor.\n6. **Q** → in-place: HRP стоит, body танцует на месте.\n\n---\n\n## Следующее\n\n- [Подключить в свою игру](docs/TUTORIAL.md) — установка через Wally + server/client/RemoteEvent\n- [Добавить новую анимацию](docs/WORKFLOW.md) — полный шаг-за-шагом гайд\n- [API reference + опции](docs/REFERENCE.md) — все settings, методы, форматы\n- [Troubleshooting](docs/REFERENCE.md#troubleshooting) — типичные баги\n- [Установка плагина](plugin/INSTALL.md) — Studio Local Plugin\n- [Вся документация](docs/INDEX.md)\n","readmeTruncated":false}