Skip to content

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 Thumbnail

Import Pipeline

The import proceeds in six steps inside MohoImporter.OnImportAsset():

  1. Unzip and parse -- opens the .moho ZIP, deserialises Project.mohoproj via Newtonsoft.Json into MohoProject, and extracts the preview.jpg thumbnail.
  2. Build prefab root -- creates a root GameObject with a SortingGroup so the entire character sorts as one unit.
  3. Build bone skeletons -- finds all BoneLayer nodes in the layer tree, then MohoBoneBuilder converts 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).
  4. Process layer tree -- MohoLayerProcessor recursively walks the layer tree creating GameObjects for each type:
    • ImageLayer: SpriteRenderer with PSD sprite resolved via MohoPsdResolver, scaled to Moho world dimensions, positioned from psd_layer_translation, bone-parented via flexi_bone_subset.
    • GroupLayer: SortingGroup with optional stencil mask group support.
    • SwitchLayer: MohoSwitchMeta with child states (only first state active by default).
    • MeshLayer (cage): Triangulated deformation cages parsed by MohoCageBuilder, wired to sibling image sprites via MohoCageDeformer.
    • MeshLayer (vector): Rasterised to textures by MohoMeshRasterizer, packed into atlases, displayed as sprites or triangle meshes with vertex colours.
  5. Generate animation clips -- MohoAnimationBuilder creates:
    • 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.
  6. 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 ConceptC# ClassNotes
Project rootMohoProjectFormat version, dimensions, FPS, camera channels
Layer treeMohoLayerRecursive via layers[], type-discriminated by type string
Bone skeletonMohoSkeleton / MohoBoneFlat array with parent index, animation channels
Animation channelMohoAnimChannelParallel when[]/val[]/interp[] arrays, with actions[] for named poses
Cage pointMohoCagePointAnimated position, width, opacity channels
InterpolationMohoInterpKeyim: 0=linear, 1=smooth, 5=step/hold

Cage Deformation

Cage deformation uses a CPU mesh approach:

  1. MohoCageBuilder parses triangulated mesh layers into Unity Mesh objects with rest positions and animated MohoCagePoint data.
  2. MohoCageBuilder.ComputeUVs() maps cage vertex positions into sprite texture space using dual UVs: UV1 for atlas sampling, UV2 for boundary clipping.
  3. MohoAnimationBuilder.AddCageCurves() bakes cage vertex positions into AnimationClip curves targeting MohoCageDeformer.vertexX/vertexY arrays.
  4. At runtime, MohoCageDeformer.LateUpdate() reads the animated arrays and writes positions to all child MeshFilter vertices.

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:

  1. MohoMeshRasterizer renders shapes at 1024 pixels/unit in three passes: scanline fill with clip masking, variable-width outlines, and stroke-only open curves.
  2. Rasterised textures are shelf-packed into atlas textures by MohoLayerProcessor.PackRasterAtlas().
  3. Triangle meshes textured from sibling PSD sprites use MohoMeshBuilder for bezier curve evaluation and the Carrot/MohoVertexColor shader 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

AssemblyNamespacePlatformReferences
Carrot.Imports.Moho.EditorCarrot.Imports.Moho.EditorEditor onlyCarrot, Carrot.Geometry, Carrot.Meshes, Carrot.Imports.Moho, Carrot.Imports.Psd.Runtime, Carrot.Imports.Psd.Editor, Unity.Nuget.Newtonsoft-Json
Carrot.Imports.MohoCarrot.Imports.MohoAll platformsNone

Key Design Decisions

  • ZIP-based format. Moho .moho files are ZIP archives. The importer reads them with System.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 AnimationClip curves driving MohoCageDeformer arrays, which write to mesh vertices in LateUpdate. 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. MohoMaterialLibrary caches materials by (Shader, Texture) key so identical sprite/mesh combinations share a single material instance.
  • Format version gating. The importer checks MohoProject.version against FormatVersions.MinSupported (hard fail) and MaxTested (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

  1. Add kids.kapish.imports.moho to your Unity project manifest. This will pull in its dependencies: kids.kapish, kids.kapish.maths, kids.kapish.meshes, kids.kapish.imports.psd, and com.unity.nuget.newtonsoft-json.
  2. Place .moho files and their referenced .psd files 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 SortingGroup for unified scene sorting.
  • A bone hierarchy under a Bones child object.
  • A layer tree under a Layers child 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

ComponentPurpose
MohoCageDeformerDrives cage mesh vertex deformation from animated vertexX[]/vertexY[] arrays in LateUpdate.
MohoImageMetaCarries PSD layer ID, identifier, file path, bounds, and flexi bone indices for image layers.
MohoSwitchMetaStores available state names for switch layers.

Shaders

ShaderPurpose
Carrot/MohoCageSpriteTransparent sprite shader for cage-deformed meshes. Uses dual UVs for atlas sampling and boundary clipping.
Carrot/MohoVertexColorTransparent 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 .moho file as they are in the Moho project.
  • Re-import after PSD changes. If you update a PSD, reimport the .moho file 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.

Carrot