Add fish-pipeline skill
This commit is contained in:
@@ -58,3 +58,20 @@ git clone https://git.shanaty.ru/Emil_Shanaty/skills.git
|
|||||||
mkdir -p ~/.agents/skills
|
mkdir -p ~/.agents/skills
|
||||||
cp -r skills/game-designer ~/.agents/skills/
|
cp -r skills/game-designer ~/.agents/skills/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## fish-pipeline
|
||||||
|
|
||||||
|
Перенос рыбы из Blender в Unity с физическим скелетом, skinning и независимыми
|
||||||
|
morph/shape-key анимациями. Содержит экспорт GLB, настройку Rigidbody/ConfigurableJoint,
|
||||||
|
нормализацию morph-весов, проверки через BakeMesh и правила адаптации к новым моделям и API.
|
||||||
|
|
||||||
|
- [Инструкции скилла](fish-pipeline/SKILL.md)
|
||||||
|
- [Проверенный опыт](fish-pipeline/references/experience.md)
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://git.shanaty.ru/Emil_Shanaty/skills.git
|
||||||
|
mkdir -p ~/.agents/skills
|
||||||
|
cp -r skills/fish-pipeline ~/.agents/skills/
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
---
|
||||||
|
name: fish-pipeline
|
||||||
|
description: >-
|
||||||
|
Переносит рыбу из Blender в Unity с физическим скелетом, skinning и независимыми
|
||||||
|
morph/shape-key анимациями. Использовать для FishGen, импорта GLB, ретопологии,
|
||||||
|
настройки Rigidbody/ConfigurableJoint, BlendShape loop, исправления надувания и
|
||||||
|
чрезмерных morph-деформаций. Адаптируется к модели и версиям API и обновляет
|
||||||
|
собственные инструкции только после проверенных результатов.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fish Pipeline
|
||||||
|
|
||||||
|
Перенос рыбы из Blender в Unity с двумя независимыми уровнями деформации:
|
||||||
|
|
||||||
|
- физический скелет двигает тело и хвост через `Rigidbody` и `ConfigurableJoint`;
|
||||||
|
- morph-анимация меняет только `blendShape`-веса в собственном loop и не управляет костями.
|
||||||
|
|
||||||
|
Не считать задачу завершённой только по успешному импорту. Проверять геометрию,
|
||||||
|
материалы, физику, morph-веса и фактическую деформацию через `BakeMesh`.
|
||||||
|
|
||||||
|
## Самообновление и адаптация
|
||||||
|
|
||||||
|
При каждом вызове:
|
||||||
|
|
||||||
|
1. Сначала читать этот файл и `references/experience.md`.
|
||||||
|
2. Проверять версии Unity, `com.unity.cloud.gltfast`, `com.unity.pipeline`, Unity CLI и Blender.
|
||||||
|
Использовать локальную документацию установленных пакетов и introspection API, а не память.
|
||||||
|
3. Не копировать параметры прошлой рыбы вслепую. Определять по данным модели:
|
||||||
|
- продольную ось и направление головы;
|
||||||
|
- размеры и масштаб;
|
||||||
|
- тип `Renderer`;
|
||||||
|
- число костей и morph targets;
|
||||||
|
- `GetBlendShapeFrameWeight` и диапазон значений кривых;
|
||||||
|
- пути `AnimationClip` и привязки кривых.
|
||||||
|
4. Если API отличается, сначала найти рабочий вызов через документацию или RNA/командную схему.
|
||||||
|
5. После успешной проверки обновлять этот skill и `references/experience.md`:
|
||||||
|
- записывать только подтверждённые симптомы, причины, исправления и способы проверки;
|
||||||
|
- устаревшие инструкции заменять, а не накапливать противоречия;
|
||||||
|
- неудачные попытки сохранять только как краткие предупреждения с итогом;
|
||||||
|
- объединять повторы и поддерживать файл компактным.
|
||||||
|
6. Приглашение пользователя к исправлению считать ошибкой pipeline. Сначала найти причину,
|
||||||
|
затем менять код или настройки. Не маскировать ошибку простым уменьшением анимации.
|
||||||
|
7. После подтверждения пользователем рабочего результата зафиксировать его как текущий baseline.
|
||||||
|
|
||||||
|
Ограничения самообновления:
|
||||||
|
|
||||||
|
- обновления выполняются только во время вызова skill и в рамках запроса пользователя;
|
||||||
|
- не запускать фоновые процессы, goals или cron для самообновления;
|
||||||
|
- не делать commit/push без явной просьбы;
|
||||||
|
- не менять пользовательские модели и анимации без необходимости;
|
||||||
|
- перед опасными изменениями сохранять исходники или делать отдельную рабочую копию;
|
||||||
|
- не записывать секреты, токены и личные данные;
|
||||||
|
- не отключать телеметрию и не менять настройки приватности.
|
||||||
|
|
||||||
|
## Текущий проверенный контур
|
||||||
|
|
||||||
|
- Unity: `6000.6.8f1`.
|
||||||
|
- `com.unity.cloud.gltfast`: `6.20.0`.
|
||||||
|
- `com.unity.pipeline`: `0.10.0-exp.1`.
|
||||||
|
- Blender: `5.2.3 LTS`.
|
||||||
|
- UPM-пакет: `/home/emil/Projects/Unity-tools/fish-gen`.
|
||||||
|
- Тестовый Unity-проект: `/home/emil/Projects/FishingGame`.
|
||||||
|
- Текущий модуль привязки: `fish-gen/Editor/FishModelBinder.cs`.
|
||||||
|
- Текущая morph-модель: `fish-gen/Models/Mesh0Animated.glb`.
|
||||||
|
|
||||||
|
Это отправная точка, а не обязательные пути для других проектов. В другом проекте сначала
|
||||||
|
определить его корень и расположение FishGen через `unity status` и Package Manager.
|
||||||
|
|
||||||
|
## 1. Подготовка модели в Blender
|
||||||
|
|
||||||
|
Сохранять исходник отдельно. Для игровой рыбы предпочтительна чистая топология и UV.
|
||||||
|
|
||||||
|
При ручной ретопологии:
|
||||||
|
|
||||||
|
- сохранять петли вдоль позвоночника и у оснований плавников;
|
||||||
|
- строить низкополигональную поверхность поверх исходника;
|
||||||
|
- использовать Shrinkwrap и перенос/запекание текстур;
|
||||||
|
- проверять границы, non-manifold рёбра и тонкие плавники.
|
||||||
|
|
||||||
|
При автоматической ретопологии не считать Decimate достаточным для анимации. Если QuadriFlow
|
||||||
|
отклоняет mesh, проверять manifold и нормали. Для сложной модели допустим Voxel/Sharp ReMesh,
|
||||||
|
но результат проверять визуально и по топологии.
|
||||||
|
|
||||||
|
### Shape keys и маски
|
||||||
|
|
||||||
|
- `Basis` не редактировать.
|
||||||
|
- Vertex Group ограничивает существующую деформацию, но не создаёт её.
|
||||||
|
- Для движения глаз и рта создавать отдельные shape keys и плавные маски.
|
||||||
|
- Для независимого morph-loop не использовать костную анимацию.
|
||||||
|
- Проверять крайние значения ключей на пересечения и растяжения кожи.
|
||||||
|
|
||||||
|
При UV Data Transfer в Blender учитывать направление: для переноса из выделенных исходников
|
||||||
|
в активный target использовать `use_reverse_transfer=True`. Предварительно проверять RNA:
|
||||||
|
имена enum и поведение оператора зависят от версии. При ошибке восстанавливать исходник,
|
||||||
|
а не продолжать с повреждёнными UV.
|
||||||
|
|
||||||
|
## 2. Экспорт GLB с morph-анимацией
|
||||||
|
|
||||||
|
Перед экспортом убедиться, что shape keys имеют кривые. Экспортировать только нужный объект.
|
||||||
|
Пример для проверенной Blender-версии:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
obj = bpy.data.objects["Mesh_0"]
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
|
||||||
|
bpy.ops.export_scene.gltf(
|
||||||
|
filepath="/home/emil/Projects/Unity-tools/fish-gen/Models/Mesh0Animated.glb",
|
||||||
|
export_format='GLB',
|
||||||
|
use_selection=True,
|
||||||
|
export_animations=True,
|
||||||
|
export_animation_mode='ACTIVE_ACTIONS',
|
||||||
|
export_frame_range=True,
|
||||||
|
export_force_sampling=True,
|
||||||
|
export_morph=True,
|
||||||
|
export_morph_animation=True,
|
||||||
|
export_apply=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
Для другой модели заменить имя, путь и режим экспорта после чтения её action/NLA.
|
||||||
|
Не запекать shape keys в mesh.
|
||||||
|
|
||||||
|
## 3. Импорт и проверка данных в Unity
|
||||||
|
|
||||||
|
Использовать `unity-cli`, `unity-pipeline` и `unity-package-management`, если они доступны.
|
||||||
|
Пакеты добавлять через Package Manager, не редактировать `manifest.json` вручную.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unity command script eval 'AssetDatabase.ImportAsset(
|
||||||
|
"Packages/com.shanaty.fish-gen/Models/Mesh0Animated.glb",
|
||||||
|
ImportAssetOptions.ForceUpdate); return "Imported";'
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- `SkinnedMeshRenderer` и материал;
|
||||||
|
- `mesh.blendShapeCount` и имена форм;
|
||||||
|
- наличие `AnimationClip`;
|
||||||
|
- `AnimationUtility.GetCurveBindings(clip)`;
|
||||||
|
- размеры mesh и направление головы.
|
||||||
|
|
||||||
|
glTFast импортирует morph-анимации, но их проигрывание и назначение путей требует
|
||||||
|
отдельной настройки.
|
||||||
|
|
||||||
|
## 4. Физический скелет
|
||||||
|
|
||||||
|
Текущий рабочий режим Fish Editor: **Tools → Fishing → Fish Editor → Test Hanging**.
|
||||||
|
|
||||||
|
Для каждого сегмента:
|
||||||
|
|
||||||
|
- `Rigidbody`;
|
||||||
|
- `CapsuleCollider` с фактическими размерами сегмента;
|
||||||
|
- `ConfigurableJoint` с предыдущим сегментом;
|
||||||
|
- ограничения углов, пружина и damping.
|
||||||
|
|
||||||
|
Первый сегмент соединять с kinematic `Mouth Anchor`. Не задавать непропорциональный
|
||||||
|
`localScale` физическому телу: размер задаётся коллайдеру, а визуальный mesh остаётся
|
||||||
|
отдельным дочерним объектом. Для короткой цепочки отключать self-collision между сегментами.
|
||||||
|
|
||||||
|
`Tail Impulse` должен прикладывать силу к кончику последнего Rigidbody через
|
||||||
|
`AddForceAtPosition`, поочерёдно меняя направление.
|
||||||
|
|
||||||
|
## 5. Skinning без повреждения morph targets
|
||||||
|
|
||||||
|
Для текущей модели ось `+Y` соответствует голове, а скелет идёт от головы к хвосту по `+X`.
|
||||||
|
Для неё используется поворот `Quaternion.Euler(0, 0, 90)`. Для другой рыбы сначала определить
|
||||||
|
ориентацию — не применять этот поворот автоматически.
|
||||||
|
|
||||||
|
Базовые правила:
|
||||||
|
|
||||||
|
- позиции вершин: поворот, равномерный масштаб и перенос начала координат;
|
||||||
|
- нормали и tangents: только поворот;
|
||||||
|
- веса вершин: плавная смесь двух ближайших костей по продольной координате;
|
||||||
|
- `bindposes[i] = bones[i].worldToLocalMatrix * visual.localToWorldMatrix`;
|
||||||
|
- источник может быть как `MeshRenderer`, так и `SkinnedMeshRenderer`.
|
||||||
|
|
||||||
|
Для каждого morph target копировать все кадры через `GetBlendShapeFrameVertices` и
|
||||||
|
`AddBlendShapeFrame`:
|
||||||
|
|
||||||
|
- `deltaVertices`: поворот и тот же равномерный масштаб, что у базовых вершин;
|
||||||
|
- `deltaNormals` и `deltaTangents`: только поворот;
|
||||||
|
- исходный `AnimationClip` не изменять.
|
||||||
|
|
||||||
|
### Два разных веса — не смешивать
|
||||||
|
|
||||||
|
**Morph-frame weight** и **animation-curve weight** — разные вещи.
|
||||||
|
|
||||||
|
В проверенном glTFast `GetBlendShapeFrameWeight` возвращал `1`, тогда Unity ожидал `100`.
|
||||||
|
Это вызывало экстраполяцию и надувание. При переносе определять диапазон и нормализовать его
|
||||||
|
к `0–100`. Если исходный диапазон `0–1`, умножать frame weight на `100`; если уже `0–100`,
|
||||||
|
не умножать повторно.
|
||||||
|
|
||||||
|
Кривые morph-анимации также могут иметь диапазон `0–1`, тогда как Unity BlendShape использует
|
||||||
|
`0–100`. Вычислять коэффициент по фактическим ключам. Текущий пользовательский уровень
|
||||||
|
амплитуды — `20%` от исходной, но сначала исправлять нормализацию и только затем настраивать
|
||||||
|
силу анимации.
|
||||||
|
|
||||||
|
## 6. Независимый morph-loop
|
||||||
|
|
||||||
|
Использовать `Animation` на том же GameObject, где находится `SkinnedMeshRenderer`.
|
||||||
|
|
||||||
|
Для каждого исходного клипа:
|
||||||
|
|
||||||
|
1. Создать новый `AnimationClip` с `legacy = true` и `wrapMode = Loop`.
|
||||||
|
2. Копировать только кривые `SkinnedMeshRenderer` с `blendShape.*`.
|
||||||
|
3. Transform-кривые исключать, чтобы анимация не боролась с физикой.
|
||||||
|
4. Путь кривой заменить на `""`, поскольку renderer находится на том же объекте.
|
||||||
|
5. Ключи копировать в новый `AnimationCurve`, не мутируя исходный клип.
|
||||||
|
6. Установить кривую через `loopClip.SetCurve`.
|
||||||
|
7. Добавить клип в `Animation`, включить loop и `playAutomatically`.
|
||||||
|
|
||||||
|
Исходный путь вида `Mesh_0` нельзя оставлять без проверки: он относится к иерархии Blender,
|
||||||
|
а не к созданному `Fish Visual`.
|
||||||
|
|
||||||
|
## 7. Обязательная проверка
|
||||||
|
|
||||||
|
После изменения C#:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unity command script recompile --project-path <project>
|
||||||
|
unity command script recompile_status --project-path <project>
|
||||||
|
```
|
||||||
|
|
||||||
|
Запустить `Test Hanging` и проверить:
|
||||||
|
|
||||||
|
- создание Rigidbody, joints, mesh и материалов;
|
||||||
|
- `blendShapeCount` и имена форм;
|
||||||
|
- `Animation.isPlaying`;
|
||||||
|
- все кривые loop указывают на `""` и только на `blendShape.*`;
|
||||||
|
- веса форм меняются во времени;
|
||||||
|
- `Tail Impulse` двигает последний сегмент одновременно с morph-loop.
|
||||||
|
|
||||||
|
Сравнить `BakeMesh` до и после morph или импульса. Для текущего baseline при `Breath = 10`
|
||||||
|
максимальное смещение вершины должно быть примерно **0.00178 м**, а не 0.17772 м.
|
||||||
|
|
||||||
|
Проверить консоль:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unity command console status --project-path <project>
|
||||||
|
unity command console get --level error --project-path <project>
|
||||||
|
```
|
||||||
|
|
||||||
|
После теста остановить Play Mode, снять временный Run In Background override и удалить
|
||||||
|
временные probe-скрипты и тестовые объекты.
|
||||||
|
|
||||||
|
## 8. Диагностика
|
||||||
|
|
||||||
|
| Симптом | Сначала проверить |
|
||||||
|
|---|---|
|
||||||
|
| Рыба надувается | `GetBlendShapeFrameWeight`, экстраполяцию и фактический `BakeMesh` |
|
||||||
|
| Анимация почти незаметна | Диапазон ключей `0–1` против Unity `0–100` |
|
||||||
|
| Глаза/рот не двигаются | `legacy`, `Animation.isPlaying`, имена форм и пути кривых |
|
||||||
|
| Анимация ломает физику | Наличие transform-кривых вместо только `blendShape.*` |
|
||||||
|
| Mesh внизу/боком | Ось модели, bindposes и направление головы |
|
||||||
|
| Текстуры испорчены | UV, направление Data Transfer, активную UV-развёртку |
|
||||||
|
| Суставы дрожат | Масштаб Rigidbody, массы, solver iterations и self-collision |
|
||||||
|
|
||||||
|
## 9. Фиксация результата
|
||||||
|
|
||||||
|
После успешной проверки:
|
||||||
|
|
||||||
|
- сохранить `.meta` вместе с ассетами;
|
||||||
|
- обновить этот skill и `references/experience.md` по правилам выше;
|
||||||
|
- убедиться, что временные probe-файлы удалены;
|
||||||
|
- commit/push выполнять только по явному запросу пользователя.
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Проверенный опыт Fish Pipeline
|
||||||
|
|
||||||
|
Обновлять только после подтверждения. Повторяющиеся записи объединять. Устаревшие правила
|
||||||
|
заменять в `SKILL.md`, а здесь сохранять краткую историю причин и проверок.
|
||||||
|
|
||||||
|
## 2026-11-09 — Базовый физический скелет
|
||||||
|
|
||||||
|
- Создана цепочка из 6 Rigidbody и 6 ConfigurableJoint с kinematic mouth anchor.
|
||||||
|
- Нестабильность уменьшается, когда физический transform не масштабируется, а размер задаётся
|
||||||
|
CapsuleCollider. Визуальная капсула находится в дочернем объекте.
|
||||||
|
- Tail Impulse через `AddForceAtPosition` к кончику последнего сегмента успешно раскачивает цепь.
|
||||||
|
|
||||||
|
## 2026-11-09 — GLB и skinning
|
||||||
|
|
||||||
|
- `com.unity.cloud.gltfast` импортирует mesh, материалы, morph targets и AnimationClips.
|
||||||
|
- Для первой модели 6 костей и 6194 вершины успешно деформировались после импульса.
|
||||||
|
- Направление модели нужно определять отдельно; поворот `Z +90` подходит только для проверенной
|
||||||
|
ориентации `head +Y` к `tail +X`.
|
||||||
|
|
||||||
|
## 2026-11-09 — Независимая morph-анимация
|
||||||
|
|
||||||
|
- Shape keys `Eye_Look_Left`, `Eye_Look_Right`, `Open_mouths`, `Breath` экспортированы из Blender
|
||||||
|
с `export_morph=True`, `export_morph_animation=True`, `ACTIVE_ACTIONS`.
|
||||||
|
- В Unity найден один 4-секундный клип с кривыми `blendShape.*`.
|
||||||
|
- Для независимого loop создан новый legacy `AnimationClip`, путь кривой изменён на пустой,
|
||||||
|
transform-кривые отброшены. Физика и morph-анимация работают одновременно.
|
||||||
|
|
||||||
|
## 2026-11-09 — Нормализация morph-frame
|
||||||
|
|
||||||
|
Симптом: рыба сильно надувалась, `Breath = 10` давало смещение вершины `0.17772 м`.
|
||||||
|
|
||||||
|
Причина: glTFast вернул `GetBlendShapeFrameWeight = 1`, тогда Unity ожидал диапазон `0–100`.
|
||||||
|
Копирование значения `1` приводило к экстраполяции в 10 раз.
|
||||||
|
|
||||||
|
Исправление: нормализовать frame weight к `0–100` при создании target mesh.
|
||||||
|
|
||||||
|
Проверка после исправления: при `Breath = 10` максимальное смещение стало `0.00178 м`.
|
||||||
|
Не уменьшать анимацию до такой проверки — сначала исправить frame weight.
|
||||||
|
|
||||||
|
## 2026-11-09 — Значения кривых
|
||||||
|
|
||||||
|
- Исходные morph-кривые имеют диапазон `0–1`, Unity BlendShape использует `0–100`.
|
||||||
|
- При переносе ключи копируются в новый `AnimationCurve`; исходный клип не изменяется.
|
||||||
|
- Текущая пользовательская сила воспроизведения — 20% от исходной амплитуды. Это настройка после
|
||||||
|
исправления frame weight, а не решение проблемы надувания.
|
||||||
|
|
||||||
|
## 2026-11-09 — UV Data Transfer в Blender
|
||||||
|
|
||||||
|
- В Blender 5.2 направление оператора критично: для selected source → active target требуется
|
||||||
|
`use_reverse_transfer=True`.
|
||||||
|
- Неправильное направление повредило UV исходника в текущей сцене; источник восстановлен из
|
||||||
|
отдельной сохранённой копии.
|
||||||
|
- Перед вызовом читать RNA: допустимые enum для `layers_select_src/dst` зависят от контекста.
|
||||||
|
|
||||||
|
## 2026-11-09 — Ретопология
|
||||||
|
|
||||||
|
- Простой Decimate не признан подходящим для анимации этой рыбы.
|
||||||
|
- QuadriFlow отклонил исходную поверхность; Voxel/Sharp ReMesh дал 16166 квадов и сохранил форму.
|
||||||
|
- UV восстановлены через Data Transfer в правильном направлении. Для скелета нужна отдельная
|
||||||
|
проверка non-manifold участков.
|
||||||
Reference in New Issue
Block a user