Game Development 22 min read

How to Build a Dynamic Face‑Customization System on Mobile with Spine

This article explains how to use the Spine 2D skeletal animation framework to implement a flexible, runtime face‑customization and outfit‑changing feature on mobile platforms, covering basic concepts, code examples, resource handling, memory optimizations, and platform‑specific integration challenges.

NetEase Media Technology Team
NetEase Media Technology Team
NetEase Media Technology Team
How to Build a Dynamic Face‑Customization System on Mobile with Spine

Spine Overview

What is Spine?

Spine is a 2D skeletal animation editor used in game development. It binds images to bones and animates the bones, storing only bone and animation data, which results in very small file sizes compared with frame‑by‑frame animation.

Small size : Only bone data is stored, producing tiny files.

Reduced art workload : Fewer assets are needed.

Smooth playback : Interpolation generates intermediate frames.

Equipment attachment : Images attached to bones can be swapped at runtime.

Mixing : Multiple animations can blend (e.g., shooting while running).

Programmatic control : Bones can be driven by code for effects such as aiming.

Spine vs. DragonBones

Cost : Spine is commercial; DragonBones is free.

Feature set : Both support IK and mesh, but Spine offers richer features such as repeated image merging and path constraints.

Stability : Spine is mature; DragonBones is newer and may contain more bugs.

Engine support : Spine provides runtimes for most major engines (Unreal, Unity, Cocos2d, etc.), while DragonBones supports fewer engines.

Basic Concepts

Bone : The fundamental element of a skeleton; bones form a tree hierarchy.

Slot : A placeholder on a bone that can hold an attachment.

Attachment : The image placed in a slot; swapping attachments changes the visual appearance.

The structure is Bone → Slot → Attachment as shown in the editor screenshot:

Spine hierarchy
Spine hierarchy

Dynamic Avatar Customization

Traditional skin‑based approach and limitation

Most projects use Spine’s built‑in skin system, where designers create a set of skins that contain predefined attachments. This works when the number of possible combinations is small, but it quickly becomes infeasible when each product (hair, nose, accessories, etc.) can be mixed arbitrarily, because the number of required skins would explode, leading to large file sizes and high memory usage.

Runtime attachment replacement

Because a character is organized as Bone → Slot → Attachment , we can replace the attachment of a slot at runtime based on the user’s selection.

public void changeStyle(String part, String style) {
    // Find the slot corresponding to the part
    String slotName = findSlotNameByPart(part);
    Slot slot = skeleton.findSlot(slotName);
    // Locate the attachment for the desired style
    Attachment attachment = findAttachment(slotName, style);
    // Replace the slot’s attachment
    slot.setAttachment(attachment);
}

Color customization is achieved by modifying the slot’s color:

public void changeColor(String part, String color) {
    String slotName = findSlotNameByPart(part);
    Slot slot = skeleton.findSlot(slotName);
    slot.getColor().set(Color.valueOf(color));
}

Preventing reset during animation

Spine calls skeleton.setToSetupPose() before each animation, which resets attachments and colors to their defaults, undoing our customizations. The reset uses the attachmentName and color fields stored in SlotData. By updating those fields together with the runtime changes, the reset no longer overwrites our data.

public void changeStyle(String part, String style) {
    String slotName = findSlotNameByPart(part);
    Slot slot = skeleton.findSlot(slotName);
    Attachment attachment = findAttachment(slotName, style);
    slot.setAttachment(attachment);
    // Update SlotData so the reset keeps the new attachment
    slot.getData().setAttachmentName(attachment.getName());
}

public void changeColor(String part, String color) {
    String slotName = findSlotNameByPart(part);
    Slot slot = skeleton.findSlot(slotName);
    slot.getColor().set(Color.valueOf(color));
    // Update SlotData so the reset keeps the new color
    slot.getData().getColor().set(Color.valueOf(color));
}

After this fix, the avatar retains its custom style and color even when the animation restarts.

Saving avatar configuration

Exported Spine JSON files are large (≈2 MB). To reduce bandwidth we define a lightweight avatar description that records only the selected part, style, and color.

{
  "version": "1.0",
  "items": [
    {"part": "hair", "style": "002", "color": "AABBCCFF"},
    {"part": "nose", "style": "005", "color": "AABBCCFF"}
  ]
}

This custom JSON is only a few kilobytes (≈4 KB). At render time we load the default Spine data, then apply the recorded style and color changes programmatically.

Dynamic resource update flow

Because product offerings change frequently, we use a pull‑based update: the server publishes a new resource package with a version number; the client checks the version at appropriate moments and downloads the updated package.

Resource update flow
Resource update flow

Mobile Integration Optimizations

Multi‑instance rendering on Android

Spine rendering on Android uses the libGDX engine, which traditionally relies on a single global App instance. We introduced an adapter that implements App for each SpineView instance. The adapter maintains a map of thread IDs to SpineView objects, allowing the global GDX variable to dispatch rendering callbacks to the correct view.

Android adapter diagram
Android adapter diagram

Multi‑instance rendering on iOS

On iOS the runtime is Cocos2d‑objc, which also uses a singleton CCDirector with a single CCGLView. We swizzled the rendering pipeline to create separate CCGLView instances for each top‑level node, avoiding changes to the engine core.

iOS view splitting
iOS view splitting

Memory consumption reduction

Large numbers of product styles increase texture memory. We applied three optimizations:

Reduce texture resolution for mobile devices.

Merge duplicate textures during Spine export.

Share a single OpenGL texture across multiple SpineView instances.

Testing on Android showed a 55 % memory reduction.

Memory usage before/after
Memory usage before/after

Conclusion

The described techniques—dynamic attachment and color replacement, SlotData synchronization, lightweight avatar serialization, pull‑based resource updates, multi‑instance rendering adapters, and texture memory optimizations—provide a robust foundation for building scalable, customizable avatar systems on mobile platforms using Spine.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationperformance optimizationResource Managementdynamic assetsmobile gamecharacter customizationSpine
NetEase Media Technology Team
Written by

NetEase Media Technology Team

NetEase Media Technology Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.