Appearance
kids.kapish.imports.moho
unity
ScriptedImporter for Moho (.moho) cutout animation files. Produces a rigged prefab with bone hierarchy, sprite bindings, and animation clips.
Architecture
The importer reads .moho files (ZIP archives containing Project.mohoproj JSON and an optional preview.jpg thumbnail) and produces a complete Unity prefab with the following structure:
Root (SortingGroup)
├── Bones
│ ├── Hip
│ │ ├── Torso
│ │ │ ├── LeftArm
│ │ │ └── RightArm
│ │ └── ...
├── Layers
│ ├── ImageLayer (SpriteRenderer + MohoImageMeta)
│ ├── GroupLayer (SortingGroup)
│ │ └── ...
│ ├── SwitchLayer (MohoSwitchMeta)
│ │ ├── State0 (active)
│ │ └── State1 (inactive)
│ └── MeshLayer (MeshFilter + MeshRenderer)
├── Timeline AnimationClip
├── Per-Action AnimationClips
└── Preview ThumbnailImport Pipeline
The import proceeds in six steps inside MohoImporter.OnImportAsset():
- Unzip and parse -- opens the
.mohoZIP, deserialisesProject.mohoprojvia Newtonsoft.Json intoMohoProject, and extracts thepreview.jpgthumbnail. - Build prefab root -- creates a root
GameObjectwith aSortingGroupso the entire character sorts as one unit. - Build bone skeletons -- finds all
BoneLayernodes in the layer tree, thenMohoBoneBuilderconverts each flat bone array into a Unity Transform hierarchy using a two-pass approach (compute world positions from Moho's rotated chain, then create GameObjects with correct local rotations). - Process layer tree --
MohoLayerProcessorrecursively walks the layer tree creating GameObjects for each type:- ImageLayer:
SpriteRendererwith PSD sprite resolved viaMohoPsdResolver, scaled to Moho world dimensions, positioned frompsd_layer_translation, bone-parented viaflexi_bone_subset. - GroupLayer:
SortingGroupwith optional stencil mask group support. - SwitchLayer:
MohoSwitchMetawith child states (only first state active by default). - MeshLayer (cage): Triangulated deformation cages parsed by
MohoCageBuilder, wired to sibling image sprites viaMohoCageDeformer. - MeshLayer (vector): Rasterised to textures by
MohoMeshRasterizer, packed into atlases, displayed as sprites or triangle meshes with vertex colours.
- ImageLayer:
- Generate animation clips --
MohoAnimationBuildercreates:- A main timeline clip from bone pos/angle/scale channels plus cage vertex curves.
- Per-action clips from named action poses on bone and cage channels.
- Cage-only action clips for actions that exist only on cage points.
- Register sub-assets -- all GameObjects, clips, materials, textures, sprites, and meshes are registered with the
AssetImportContext.
Model Layer (JSON Mapping)
All classes in Editor/Model/ map directly to Moho's JSON format using Newtonsoft.Json with [JsonExtensionData] on each class to preserve unrecognised fields for debugging.
| Moho Concept | C# Class | Notes |
|---|---|---|
| Project root | MohoProject | Format version, dimensions, FPS, camera channels |
| Layer tree | MohoLayer | Recursive via layers[], type-discriminated by type string |
| Bone skeleton | MohoSkeleton / MohoBone | Flat array with parent index, animation channels |
| Animation channel | MohoAnimChannel | Parallel when[]/val[]/interp[] arrays, with actions[] for named poses |
| Cage point | MohoCagePoint | Animated position, width, opacity channels |
| Interpolation | MohoInterpKey | im: 0=linear, 1=smooth, 5=step/hold |
Cage Deformation
Cage deformation uses a CPU mesh approach:
MohoCageBuilderparses triangulated mesh layers into UnityMeshobjects with rest positions and animatedMohoCagePointdata.MohoCageBuilder.ComputeUVs()maps cage vertex positions into sprite texture space using dual UVs: UV1 for atlas sampling, UV2 for boundary clipping.MohoAnimationBuilder.AddCageCurves()bakes cage vertex positions intoAnimationClipcurves targetingMohoCageDeformer.vertexX/vertexYarrays.- At runtime,
MohoCageDeformer.LateUpdate()reads the animated arrays and writes positions to all childMeshFiltervertices.
The Carrot/MohoCageSprite shader samples the atlas texture and discards fragments outside the sprite's [0,1] UV2 range.
Vector Mesh Rasterisation
Non-cage mesh layers (Moho's vector art) are rasterised to textures:
MohoMeshRasterizerrenders shapes at 1024 pixels/unit in three passes: scanline fill with clip masking, variable-width outlines, and stroke-only open curves.- Rasterised textures are shelf-packed into atlas textures by
MohoLayerProcessor.PackRasterAtlas(). - Triangle meshes textured from sibling PSD sprites use
MohoMeshBuilderfor bezier curve evaluation and theCarrot/MohoVertexColorshader with stencil support for mask groups.
PSD Dependency
The importer depends on kids.kapish.imports.psd for sprite data. PSD files referenced by image layers are registered as artifact dependencies via ctx.DependsOnArtifact(), ensuring they import before the Moho file. MohoPsdResolver loads PSD sub-assets and resolves sprites by parsing the psd_{layerId}_{name} naming convention.
Assemblies
| Assembly | Namespace | Platform | References |
|---|---|---|---|
Carrot.Imports.Moho.Editor | Carrot.Imports.Moho.Editor | Editor only | Carrot, Carrot.Geometry, Carrot.Meshes, Carrot.Imports.Moho, Carrot.Imports.Psd.Runtime, Carrot.Imports.Psd.Editor, Unity.Nuget.Newtonsoft-Json |
Carrot.Imports.Moho | Carrot.Imports.Moho | All platforms | None |
Key Design Decisions
- ZIP-based format. Moho
.mohofiles are ZIP archives. The importer reads them withSystem.IO.Compression.ZipFile, avoiding any native dependencies. - Newtonsoft.Json with extension data. Every model class uses
[JsonExtensionData]so unrecognised fields are preserved rather than silently dropped. This makes the importer resilient to format changes. - CPU cage deformation. Cage vertex animation uses
AnimationClipcurves drivingMohoCageDeformerarrays, which write to mesh vertices inLateUpdate. This avoids compute shader dependencies and works on all platforms. - Dual-UV cage shader. UV1 maps into atlas texture space for sampling; UV2 provides [0,1] normalised coordinates for boundary clipping. This allows cage meshes to extend beyond the sprite without rendering garbage pixels.
- Material deduplication.
MohoMaterialLibrarycaches materials by(Shader, Texture)key so identical sprite/mesh combinations share a single material instance. - Format version gating. The importer checks
MohoProject.versionagainstFormatVersions.MinSupported(hard fail) andMaxTested(warning), providing clear diagnostics for unsupported files.
Usage Guide
ScriptedImporter for Moho (.moho) cutout animation files. Produces a rigged prefab with bone hierarchy, sprite bindings, and animation clips.
Setup
- Add
kids.kapish.imports.mohoto your Unity project manifest. This will pull in its dependencies:kids.kapish,kids.kapish.maths,kids.kapish.meshes,kids.kapish.imports.psd, andcom.unity.nuget.newtonsoft-json. - Place
.mohofiles and their referenced.psdfiles in your Assets folder, preserving the relative paths used in Moho.
Import Workflow
Drop a .moho file into your project. Unity will automatically import it using MohoImporter, producing:
- A root prefab with a
SortingGroupfor unified scene sorting. - A bone hierarchy under a
Boneschild object. - A layer tree under a
Layerschild object containing sprites, groups, switches, and meshes. - A timeline AnimationClip with all bone transforms and cage deformation curves.
- Per-action AnimationClips for each named action defined in the Moho file.
- A preview thumbnail extracted from the archive.
PSD Placement
Image layers in Moho reference PSD files by relative path. Place PSD files at the same relative path from the .moho file as they are in the Moho project. The importer registers PSD dependencies automatically so they import first.
Working with the Imported Prefab
Instantiating
csharp
using UnityEngine;
public class CharacterSpawner : MonoBehaviour
{
public GameObject mohoPrefab;
void Start()
{
GameObject character = Instantiate(mohoPrefab, transform);
}
}Playing Animations
Animation clips are embedded as sub-assets. Use an Animator or Animation component to play them:
csharp
using UnityEngine;
public class CharacterAnimator : MonoBehaviour
{
public AnimationClip idleAction;
public AnimationClip walkAction;
private Animation anim;
void Start()
{
anim = GetComponent<Animation>();
anim.AddClip(idleAction, "Idle");
anim.AddClip(walkAction, "Walk");
anim.Play("Idle");
}
}Reading Switch States
Switch layers carry a MohoSwitchMeta component listing available states:
csharp
using Carrot.Imports.Moho;
using UnityEngine;
public class SwitchController : MonoBehaviour
{
void Start()
{
MohoSwitchMeta switchMeta = GetComponentInChildren<MohoSwitchMeta>();
if (switchMeta != null)
{
Debug.Log($"Available states: {string.Join(", ", switchMeta.stateNames)}");
}
}
}Accessing Image Layer Metadata
Each image layer has a MohoImageMeta component carrying PSD reference data:
csharp
using Carrot.Imports.Moho;
using UnityEngine;
public class LayerInspector : MonoBehaviour
{
void Start()
{
MohoImageMeta[] metas = GetComponentsInChildren<MohoImageMeta>();
foreach (MohoImageMeta meta in metas)
{
Debug.Log($"Layer {meta.psdLayerId}: {meta.psdLayerIdentifier} from {meta.imageFileRefPath}");
}
}
}Cage Deformation
Cage-deformed sprites use MohoCageDeformer, which is driven automatically by animation clips. If you add or remove child meshes at runtime, call RefreshChildMeshes():
csharp
using Carrot.Imports.Moho;
using UnityEngine;
public class CageSetup : MonoBehaviour
{
void Start()
{
MohoCageDeformer deformer = GetComponentInChildren<MohoCageDeformer>();
if (deformer != null)
{
deformer.RefreshChildMeshes();
}
}
}Runtime Components
| Component | Purpose |
|---|---|
MohoCageDeformer | Drives cage mesh vertex deformation from animated vertexX[]/vertexY[] arrays in LateUpdate. |
MohoImageMeta | Carries PSD layer ID, identifier, file path, bounds, and flexi bone indices for image layers. |
MohoSwitchMeta | Stores available state names for switch layers. |
Shaders
| Shader | Purpose |
|---|---|
Carrot/MohoCageSprite | Transparent sprite shader for cage-deformed meshes. Uses dual UVs for atlas sampling and boundary clipping. |
Carrot/MohoVertexColor | Transparent vertex-colour shader for rasterised vector meshes. Supports stencil operations for mask groups. |
Tips
- Relative paths matter. PSD files must be at the same relative path from the
.mohofile as they are in the Moho project. - Re-import after PSD changes. If you update a PSD, reimport the
.mohofile to pick up new sprites. - Format versions. The importer supports Moho format version 1045 and above. Older files need to be re-saved in a newer version of Moho.
- Animation clips are read-only sub-assets. To modify clips, duplicate them out of the imported asset.