Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Documentation for some of the functionality provided with the ODK plugin
Building Otherside, one blueprint at a time.
Full ODK Blueprint node library with descriptions for each node. https://odk-blueprints.preview.msquared.io/v9.3
High-level 101
The Otherside Development Kit (or ODK) is a platform built for the Yuga community to build and share experiences within the Otherside community.
The ODK is built on top of MSquared's World Builder platform. World Builder documentation is available here. The World Builder is built on top of a Blueprint only version of Epic's Unreal Engine, and is architected to allow quick iteration on content, while also providing unparalleled player number and quick and easy travel between worlds.
It's highly recommend that if you intend to develop for the ODK that you familiarize yourself with the basics of the World Builder platform. In particular, sections that are useful to read are:
The ODK also provides a host of Otherside related content built on top of the World Builder. The majority of the functionality is provided within , which is located within the engine plugins of your ODK Unreal installation
Please peruse the provided content, and use the as means to explore what's available within the ODK.
Welcome to the Otherside Development Kit!
If this is your first time here, we recommend setting up an example project from one of our templates and experimenting with what's possible.
Otherwise take a look at the plugin and templates that are provided with the Otherside Development Kit
It is HIGHLY recommended you read Msquared's documentation to familiarise yourself with some core concepts within the World Builder that the ODK builds on: https://docs.msquared.io/ The sections that are partcularily important are
Our guide to example the Token Gating system can be found here: We have three examples of Token Gating in the Boneyard Experience.
The clubhouse as pictured above gates entry for Bored Ape holders only, these is a good way of adding areas into your experience that are only accessible by owners of specific NFT collections.
The above shows the Toilet ontop of the clubhouse, we gate this as part of the quest "Drop a Deuce" which can be started via the Quest Giver desk. We gate entry to this Toilet unless the player has a Drop a Deuce token, this is a great example of how to incorporate token gating as part of a quest, where players can only access at a specific stage of the quest or once completed.
The above force field is just bound to requiring having 1 of each of the three coins scattered around the level (x1 bronze, x1 silver and x1 gold) its a good example of how we can leverage token gating until you have certain pickups or maybe items from a vendor.
In this documentation we've set up a few sections to go over:
- Quick overview of some common 3D terms and approaches
- Basic 3D asset specifications
- General Guides for how to create good character art for Otherside
- A full tutorial of creating a complete avatar collection.
- Overview of how to get your 3D assets to be available in the Otherside.
The Otherside and ODK is built on top of the Msquared network. Full documentation on their Avatar system here → . This guide is meant as a QuickStart resource to guide ODK developers in creating a full NFT collection of Avatars.
While there are many ways to build assets for digital worlds, this information will present comprehensive guidelines on how to make sure you can build and maintain avatar collections for Otherside.
After you have made all the 3D assets for an avatar collection there are four steps to follow to make them available for your holders in the Otherside.
Quick-start guide on how to download the ODK and start developing!
Access the download page and click "Install the Launcher".
The ODK Launcher should open and start downloading the ODK.
When the download is complete, the template selection window should open with the option to load an existing project or create a new project from the ODK template:
The creator portal is used to create the on-chain tokens and metadata for that token. This includes its name, description, image and any additional fields required for gameplay.
Go to: 👉
This web interface allows you to define and mint tokens for use in-game. ⚠️ Note: The portal only shows objects created by the currently logged-in user.
Some common sense things that we allow/don't allow with Custom Avatars and content in general:
Prohibited Content: Otherside bans the use of its platform to publish certain content, including items that infringe intellectual property rights, are stolen, are misleading, or violate laws and regulations. The company encourages users to report any content suspected of violating these rules.
Moderation Actions: When prohibited content is reported or detected, Otherside may:
Limit the visibility or feature status of the content.
To provide starting points, and examples for how to build functionality, the ODK is shipped with a handful of template projects.
ODK Empty Template: This template provides the minimum content required to startup an ODK project. Use this project once you're familiar with the ODK process and workflows, and are looking to start a new project.
ODK Boneyard Template: This template provides examples usages of some of the content.
ODK Combat Template: This template provides examples of how you can implement a high-fidelity shooter within the ODK.
Disable buying, selling, swapping, or transferring of the item.
Remove Otherside’s fee from such content, so it isn’t monetized.
Fully delist tokens, NFTs, or collections and hide them from discovery if the content poses user harm, making them inaccessible on the platform.
Intellectual Property Infringement: Otherside follows laws such as the DMCA for copyright-related removals. Reports must be submitted with specific details, and content is taken down upon verification. Repeat infringers risk losing their accounts.
Misleading Content: The platform prohibits content that impersonates brands, celebrities, or other collections in ways that mislead users, including fake verification marks or harmful links. Collections must clearly state if they are unofficial, and users can report misleading items.
Other Categories (Hate, Exploitation, Spam, etc.): Otherside prohibits hate speech, spam, sexual exploitation, and similar harmful content. Users can report prohibited material directly from the platform.
Moderation Process: Otherside uses a combination of user reports, automated detection, and manual review by a specialized Trust and Safety team. The team employs tools like image recognition and collection analysis to flag or remove problematic content and responds to reports, aiming to do so within a certain time frame.
Transparency and Enforcement: Otherside explicitly reserves the right to moderate, delist, or hide content and disable features at its discretion for any violations of policies laid out in their Terms of Service.
The ODK is further subject to the terms found at https://yuga.com/terms.





The MMLs contain some information about what to do with an the GLB. A simple MML that only points to a single GLB would look like:
You should create 1 MML for each token in your collection.
Each token needs to have an additional field in it's data call MML. For example if I had a fake collection called Boxie it might have metadata that looks like:
This is what allows Otherside games to pull in a users avatar that they have in their wallet.
Currently collections are added via a whitelist once steps 1-3 are complete we can review and add the contract address to our whitelist to make the avatars available for anyone who has them in their wallet.
Please fill out this form to initiate a review https://forms.gle/NNzGR22wVgBJZECbA .
Once your form is filled out, please join the Otherside discord to open a ticket and we'll use that channel to relay any feedback. If you do not open a ticket, we won't be able to integrate your collection into Otherside, so please make sure this step is completed.
<m-character src="https://fake-collection-api.io/glbs/1.glb"></m-character>{
"id": 0,
"name": "Boxie 0",
"image": "https://fake-collection-api.io/images/token_id.jpg",
"mml": "https://fake-collection-api.io/mml/token_id.mml",
"attributes": [
{
"value": "classic box",
"trait_type": "head"
},
{
"value": "metallic purple",
"trait_type": "upper"
},
{
"value": "dark",
"trait_type": "lower"
}
]
}MML Avatar Tool - https://github.com/mml-io/avatar-tools (only if completing the pipeline section)
ODK Unreal Editor (optional for testing)
Give your project a name, and click "Create New Project". The template will download and open automatically.
Once open, you're able to use Play-in-Editor to test and develop your experiences!
It only takes a quick few steps to create a deployment from your project content that others can join.
Click the "Upload Content" button within the editor dashboard
Give your world a description, and ensure that the Boneyard map is selected. Then click the "Upload" button
Your project will then be cooked, packaged and uploaded to the Otherside dashboard. In future uploads, this process will be quicker as not all content will need to be recooked. Once complete, click the "View upload in dashboard" button
That will take you to the ODK dashboard, which will display your world's uploads. You can then click "Launch World" to create a deployment from your uploaded content, and share that with others to join

How to test it?
Ensure your input mappings are being used in PIE.
Date of change: 21/01/2025
Affected Features: Transfer of Data Objects to player collection
What’s broken and why?
The event Server_TransferObjects has been deprecated and should be replaced with Server_TransferDataObjects. This event allows for a client side callback with a success/failure message.
How to fix it?
Please use the BindToDataTransferComplete function and the Server_TransferDataObjects event instead of the Server_TransferObjects event.
When calling the BindToDataTransferComplete function it will return the current auth user ID and a TransferID that will be required for the server transfer.
How to test it?
After replacing the deprecated events in your blueprints, you will receive a callback with a success or faulure message.
To mint or grant tokens, you need Gas—a small transaction fee.
Scroll to the bottom of the page
Paste in a granting wallet address (your producer will provide one)
Click Save
💡 Improbable provides internal wallets that are pre-funded to allow token operations during development.
Click Define new Object. This will take you to the Object creation flow. The objects are created on a per project basis. This means that you will have to select your project from the drop-down before the template buttons are activated.
Once your token has been defined, you'll want to start crediting it to deserving users. There are two ways to achieve this currently:
Within the Creator portal you can use the Grant button to grant an instance of your token to any provided wallet.
Within Unreal you can use the RequestGrantToken on the user's BPMC_ODK_WalletComponent to grant the token to the user. Item Id is the token id.
How do I get access to the ODK?
Please reach out to your representative at Yuga who can organise your onboarding, as part of this process you need to supply all emails that will need access to the ODK tooling.
Once you have been given access please follow the quick start guide which can be found here: https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/getting-setup-with-odk-development
When setting up a new project I get this error "Error getting space required: Error: Expected string at Index 6"
Please log in with Otherside and NOT Msquared as shown in I am unable to Login to the ODK Launcher
I am unable to Login to the ODK Launcher
Please ensure that you are logging in with the email that you have sent to your Yuga contact to get whitelisted. If this step has been completed please ensure that you are logging in via Otherside and not Msquared on the Launcher. There is also a known issue where it may take two attempts to login. If you are still facing problems then please reach out to your Yuga representative.
I am unable to enter admin mode within the engine.
Please review the
For any role that you want to have access to the Inspector (historically just Director) add the Capabilities.Morpheus.InspectorEnabled capability to the GrantedCapabilities field within your data table for those roles.
When I open the Launcher it get stuck on the spinner
First you want to confirm that you have the latest launcher installed which you can download from the dashboard, then if that doesnt resolve it please follow the guide for clearing credentials here:
I am unable to sign in via the Editor
Please follow the guide here:
If you continue to encounter sign-in failure, please collect your unreal logs and share them in your ODK support channel.
I am unable to open the web overlay within my experience/or my account details are incorrect on privy on the overlay/ or I am getting errors when opening the overlay.
With the move to using Privy the way you access deployments has changed, if you go in via the Otherside Dashboard you won't go through the Privy auth process, so when access deployments please append the following link with your WorldID and ProjectID. Please note you don't need the [] on either project ID or world ID
We've noticed on rare occasion the web browser can fail to process the content URL that it's attempting to display. We're continually working to resolve these types of errors. If you do encounter this error, please toggle the web browser to resolve (pressing Tab). If the error persists, please raise it within your ODK support channel.
Below are the technical requirements and supported features for avatars in Otherside. Following these ensures your assets import cleanly and display correctly.
3D Model Format: GLB (binary glTF)
Texture Formats: JPG or PNG
Shading Model: PBR (Physically Based Rendering)
UTF-8 encoding
Only the Basecolor map is required. Other maps (Normal, ORM, Emissive) are optional but can greatly enhance visual fidelity.
👉 Important: Only include Emissive if your avatar actually has glowing elements.
Please find the breaking changes for each ODK version listed as a sub page to this one.
ODK has M2 as a dependency, so please always refer to MSquared Breaking Changes when upgrading versions.
Below is the ODK to MSquared version guide
v9.2
v38.1.3
v9.1
v38.1.2
v9.0
v38.1.2
As part of being part of the ODK ecosystem, you get some fairly extensive Avatar management out of the box. There's a few pieces to what's available, that we'll go through here.
Currently we've proved avatar implementation for the following collections:
Bored Ape Yacht Club
Meebits
Kodas
Moonbirds
There are also individual avatars meshes available for the following collections:
Mutant Ape Yacht Club
Voyagers (Otherside)
If you log into an experience with an account that has a wallet linked with a relevant token, you'll be able to select that avatar as your in-game character from the .
Both the Koda and Moonbirds collections also have unique Animation Blueprints to allow custom animations (beyond the standard Otherside animations set).
More collections will be brought online as the ODK matures.
To provide dynamic access to a vast array of dynamic characters, at runtime we download and encode the relevant character meshes from their definitions. This implementation allows us to utilize our Carnival renderer which allows us to scale the number of unique characters in a world at once to 10,000+ !
Current supported avatar types are tracked within the BPE_AvatarType enum within Unreal.
The flow for selecting a player is as follows.
Retrieve the player's current selected avatar token via the KVStore within BPC_ODK_ProfileOverlayHandling .
Once the avatar type has been verified, request the character load on BPMC_AvatarInfo. This component manages loading the specific GLB on the M2M_CharacterAssetComponent as well as replicating the avatar type information out to observing clients.
BPM_ODK_PlayerCharacterBase listen for avatar info updates, and then calls
If you're interested in observing when a player's avatar type changes, then bind to the AvatarTypeUpdated event on the BPMC_AvatarInfo component.
If you're interesting in modifying the default avatar behaviour on clients, then overriding the RefreshAvatarTypeAnimState on your ODK character will allow you to react to the default avatar behaviour.
Max Texture Size: 2048 × 2048
Max Tri/Poly Count: 50,000
Max Avatar File Size (on disk): 20 MB. Recommended File Size: <10 MB.
Physical Size: Max Height 2 meters Min Height 1 meter
Emissive
Defines areas of the model that emit light (e.g., glowing eyes, neon strips). Use sparingly, only when necessary.
sRGB
E
Channel
Description
Colorspace
Token
Basecolor
Defines the visible color and pattern of the model. This is the only required channel.
sRGB
BC
Normal
Adds surface detail without increasing polygon count. For example, wrinkles in clothing or grooves in armor.
Linear
N
ORM (Packed)
A single texture that combines three grayscale maps: Occlusion – Adds baked-in shadows and depth. Roughness – Dark = shiny surface, Bright = rough surface. Metalness – Dark = non-metal (plastic, paint), Bright = metallic. Packing these maps into one texture reduces file size and improves performance.
Linear
ORM
v8.2
v37.0.0
v7.0
v34.1.1
v6.1.0
v33.0.3
v6.0.0
v33.0.1
v5.0.0
v31.0.0
v4.0.0
v30.0.0
v3.1.0
v29.0.1
v3.0.0
v28.0.1
v2.0.0
v26.0.0
v1.1.0
v25.0.0
v1.0.1
v24.0.0
v1.0.0
v24.0.0
RefreshAvatarTypeAnimState









Role promotion can be used to change the game experience for an individual player. It can be used to give some players extra abilities or visuals. In the Meebits Combat Gym, the player who has the highest score on each team gets promoted to the "Titan" role. This role simply increases the player's size. This serves as a demonstration as to how a role can be granted to a player. Inside BPM_TitanPromoter, this simple logic is run on the server in order to promote a player to a "Titan".
Let's talk about the characters who you'll run into on Otherside
Kodas create Voyagers out of Soma or Chaos. They are the exoskeletons for these two forces, enabling them to move around and explore Otherside.
Soon, these Voyager exoskeletons will be upgraded to be customizable exoskeletons for Soma and Chaos.
In the world of Otherside, Kodas and Voyagers work together so that people can bring in their own characters and build their own worlds.
In this documentation, we'll sometimes refer to Custom Characters as Third Party Avatars (not created by Yuga Labs or one of its partners).
In the mainline Otherside experience, the use of custom characters must be unlocked.
In the ODK experiences, all custom characters are available to experience designers. While experience designers may place some limits, the core technology is designed for maximum interoperability.


The ODK provide text and voice chat moderation services that allow you to moderate users within your worlds. The moderation page is available here.
Please reach out to Yuga to request access to the moderation page.
Double check that the MMLs are encoded with UTF-8 and not a something like UTF-8 with BOM.
Check the output logs in editor tab for more details on possible errors. - Make sure the rig uses the exact bone naming and positions from the reference skeleton. - Do not include any additional bones or unsupported structures. - Make sure your GLB files are publicly accessible (avoid CORS issues)
The ODK widget has some useful built in functionality.
Defining IMCs
You can use InputMappingContexts to define the IMCs that should be enabled when this widget is Activated. All other IMCs will be disabled. When the widget is Deactivated, the IMCs enabled previous to the widget being Activated will be re-enabled.
UI Mode
You can define the UI mode that widget needs when Activated. Generally, if you need some button input for your widget, you will want to use input mode GAME_AND_UI.
Getting the last input type
GetLastInputType can be used to find out what the last input type was (Gamepad/Keyboard etc).
You can derived your widgets from WBP_ODK_Widget. The InputMappingContexts property WBP_ODK_Widget can be used to configure which IMCs should be active when the widget is on screen. This will use the BPC_ODK_InputComponent to push a new IMC state onto the stack when the widget is displayed on screen. When the widget is closed, the state will be removed enabling whatever IMCs where active before the widget was opened.
Initializing the flow
Coordinating triggers, conditions, tasks, and actions
Tracking and updating the flow's progression
It is placed in the level as an actor so that only tasks that can be completed in the current map are shown on the HUD.
You’ll configure a few key variables on this actor:
WaitForBootflowToFinish – Whether to wait for bootflow before initializing
TriggerDelay – Optional delay (in seconds) before the flow begins
FlowTrigger – The executor that kicks off the flow (see below)
TaskFlowDataAsset – The asset that defines your actual tasks and logic
The FlowTrigger variable must be set to an executor that inherits from BP_ODK_FlowTrigger_ExecutorBase or BP_TaskFlowTrigger_TokenId when setting up a Quest.
The InitializeTaskFlowExecutor event should be used as you would BeginPlay, to set up the trigger and make bindings etc.
Once your trigger logic finishes, it must call FlowTriggerCompleted
This tells the system:
Where to start (via Task Index, e.g. resume from save)
How to proceed (StartFlow, RetryFlow, CompleteFlow)
You can use an existing trigger executor or make your own to suit your feature.
Create a new data asset that inherits from PDA_ODK_TaskFlow.
This defines the structure and logic of your flow. Inside the asset, configure:
Flow Name
Conditions – (flow-level)
Tasks – An array of individual task executors
Retry – Optional executor to handle retry logic
Actions – Optional logic fired at various moments
Custom Details – Optional data for flow-specific needs
This asset is what makes the flow dynamic and reusable.
Each task should be a class derived from BP_ODK_TaskFlow_Task.
You’ll configure:
Whether the task is enabled
Optional conditions (task-level)
One or more Complete Task Triggers
This is the most important step. The Complete Task Triggers contain all the logic to define if the task is complete
Optional Startup Delay
Optional Actions tied to task lifecycle
Each task should:
Implement InitializeTaskFlowExecutor()
Call TaskTriggerExecutionComplete() when finished
If your flow should support retries (e.g. when a player fails, cancels or completes the flow), create a retry executor that inherits from BP_ODK_RetryTaskFlow_ExecutorBase.
This executor:
Is triggered after a FlowTrigger completes with a retry state
Should call RequestRetryTaskFlow() when ready to restart
You can implement logic like displaying a retry prompt or auto-restarting silently.
Actions are “one-shot” events that happen at defined flow or task points.
To create one, derive from BP_ODK_TaskFlowAction_ExecutorBase, and implement InitializeTaskActionExecutor
Attach them to:
Task-level or flow-level lifecycle events
Visuals, sound effects, UI, cleanup, etc.
These don’t block progression and are never cached.
Enable verbose output with the live config value ODK.TaskFlows.LoggingEnabled
This will print task and flow events to the log, which is helpful when troubleshooting setup issues.
How to add visual effects to characters via ODK components
The ODK uses the BPC_ODK_NiagaraEffectsManagerComponent to play local and replicated effects on characters. The BPMC_ODK_GucciTrailHandler is an example showing how we add and remove effects to characters that own a Koda pendant NFT.
Inside BPMC_ODK_GucciTrailHandler::TryApplyGucciTrailEffect, we use the BPC_ODK_NiagaraEffectsManagerComponent to add the Gucci Niagara visual effect to both characters feet using AddNiagaraEffect. It's parameters are as follows:
Niagara System - The Niagara system to add.
Socket - The socket on the character mesh to attach the Niagara system to.
Relative Transform - The transform which should be applied to the Niagara system relative to the socket. This lets you offset/rotate or scale the visual effect in relatioin to the defined socket.
Is Persistant Effect - If true, the visual effect will play continuously until removed. If false, the effect will run once and then be automatically removed.
Replicate - Should the effect be visable to other players as well.
Wallet views allow you to filter the tokens in your wallets. If you want to get all koda cam photo tokens, you can use wallet views for that.
To get tokens in a view, simply call GetWalletView on the BP_ODK_WalletWorldService. You will need to pass in the WalletViewClass for the wallet view you care about.
Date of change: 16/07/2025
Affected Features: Users with existing ODK projects
What’s broken and why?
Currently ODK schema is embedded within the `ODK` section within the project.schema. For legacy reasons, the ODK schema was instantiated with each template, rather than stored with the plugin content itself. To ensure you're operating with the latest, correct ODK schema, please integrate the latest schema here into your project. This process will be corrected in the ODK v9 release so users don't need to manually manage this at that point
If you haven't already downloaded and installed blender go ahead and do that now.
We are using Blender 4.5 so all menu placement and controls will be aligned to this version of Blender. You are welcome to use another version, some things might have changed slightly in the program based on which version you are using.
Date of change: 08/12/2025
Affected Features: Morpheus Arrays
What’s broken and why?
Morpheus Arrays in BPs will no longer have their inner type default to integers.
This is due to a refactor in the Morpheus Arrays inner type to integrate with Unreal's redirectors preventing crashes when redirected types are used as an Inner type to the MorpheusArray.
How to fix it?
Documentation for ODK UI mode control
When opening a widget or in other various scenarios, you may want to the mouse to become visable and allow the player to interact with widgets on screen using the mouse. To do this, use the blueprint function library functions: MarkContextNeedsUIMode and UnmarkContextNeedsUIMode respectively.
You must provide a "Context" when calling these functions. This context represents the object who wants UI mode to be enabled/disabled.
If multiple calls are made to MarkContextNeedsUIMode with different contexts, calling UnmarkContextNeedsUIMode once with one of the contexts will not disable UI mode. The system will always ensure the highest UI mode is enabled that at least on context needs.
BPM_ServerPhysicsObject is a blueprint actor that can be derived from to create gameplay props that have server auth physics. This means that any player can interact with the object and have the physics affects get replicated to all other players. Examples of derived BPM_ServerPhysicsObject classes include: BPM_Dice, BPM_Ball.
The default IMCs used by derived BP_ODK_PlayerCharacterBase assets can be configured by overriding the GetInputMappingContexts function. Simply return a list of IMCs with a configured priority. You can call the base class function to include the IMCs used by the base if you like.
A vertex (plural: vertices) is a single point in 3D space.
Imagine pinning down corners on a piece of fabric—those pins are like vertices.
Vertices are connected to form edges (lines), and edges combine to make faces (triangles).
A mesh is made of thousands of these little triangles.
More vertices = more detail, but also a heavier file.
👉 You can think of vertices as the “atoms” of a 3D character—everything is built from them.
A mesh is the 3D shape of your character. Think of it as the “skin” made of many small triangles stitched together.
The mesh defines what your character looks like on the outside.
High-polygon meshes (lots of vertices) look smoother, but can be heavy and slow to load.
Lower-polygon meshes (fewer vertices) load faster, but may look blockier.
For games, we want a balance: lightweight but still nice-looking.
The skeleton, sometimes called an armature or rig, is a hidden set of bones inside your mesh.
Just like real bones, these control how your character moves.
When the skeleton moves, the mesh moves with it.
Without a skeleton, your character is a statue that can’t animate.
Weighting (or skinning) tells the computer which parts of the mesh follow which bones.
For example: your hand mesh should follow the hand bone, but not the foot bone.
Weights are often painted in colors (blue = no influence, red = full influence).
This ensures smooth bending—like your elbow flexing without breaking the arm mesh.
A texture is a 2D image (like a picture) wrapped around the mesh, giving it color, details, or patterns.
A material tells the game how the texture should look—shiny, matte, transparent, etc.
Example: The texture is the “jeans fabric image,” while the material makes it look like real denim.
GLTF is a standard file format for sharing 3D models.
GLB is the “binary” version—everything (mesh, skeleton, textures, animations) is bundled into one file.
Think of GLB as a zip file for your character, ready for upload.
MML stands for Metaversal Markup Language. It's like an HTML file but instead of a website it is a 3D object or scene
Here are some things to keep in mind as you build your avatar. Following these guidelines will save time and help your character look great once it’s in Otherside.
Animations in Otherside are built for humanoid characters (like Apes or Voyagers).
👉 If proportions are too far from human, animations may look strange.
Example: If your character’s arms reach the ground, a clapping animation will look broken.
Guidelines for best results:
Overall height must be between 1 meter and 2 meters.
Head, body, and limb sizes should be similar to human proportions.
Stylized designs (bigger heads, slightly shorter legs) are fine, as long as the character still feels humanoid.
⚠️ Note: For characters like Kodas, Yuga created a custom animation set to match their proportions. Custom animations are not available for other collections, so sticking to humanoid shapes is key.
Transparent objects are difficult to support and may not display correctly in all situations.
If possible, design around transparency instead of relying on it.
Features like glowing, metallic, or opaque materials are much easier to implement reliably.
👉 You’ll have a smoother experience if you avoid transparency altogether.
Building an avatar has many steps, and each character can present unique challenges.
A great strategy is to:
Start simple (a rough block out).
Get it running in Otherside as soon as possible.
Refine and improve in small steps.
This way you’re always testing your work in context, and you won’t waste time polishing something that doesn’t export or animate correctly.
👉 By focusing on proportions, smart material choices, and iterative testing, you’ll give yourself the best chance of success.
Start by building a basic silhouette of your avatar and attaching it to a skeleton.
This doesn’t need to be fancy—simple shapes like cubes and spheres are enough.
Weighting is easy here: you can assign each shape’s vertices to a single bone.
Export this rough version early to check:
Proportions (does it look human-like enough for animations to work?)
Skeleton setup
Scale and export settings
👉 Spend time here adjusting proportions until it feels right. It’s much faster to test and tweak now than later.
If you plan to use special materials—like metallic surfaces, glowing parts, or transparency—test them on your block out first.
This gives you a quick preview of how these materials behave in-game and helps you decide how to build the final look.
Use the information from your block out and material tests to plan your actual assets.
Single avatar: Prepare reference images and refine your block out into the real design.
Avatar collection: Decide how traits will fit together (hair, clothing, accessories, etc.) and collect references for each piece.
Good planning here saves a lot of time later in production.
If you’re creating a collection, think about how traits will be combined into GLBs.
A good starting point: keep all the traits inside a Blender project.
Use Blender’s built-in Python scripting to export specific trait combinations into GLBs, guided by your metadata.
This step ensures your process can scale smoothly from one avatar to many.
Now it’s time to create the real content.
Model and sculpt your meshes.
Bind (weight) them to the skeleton.
Create and apply textures.
If you’re just making a single avatar, all of this can happen directly in Blender. For larger projects, you may use a more complex pipeline, but the goal is the same: by the end of this step, you’ll have meshes skinned to the skeleton and textures ready for export.
Always test your avatars before release in an Otherside/ODK map.
Check weighting (do joints bend smoothly?).
Test animations (does the character walk, run, and emote correctly?).
Verify materials (are they displaying as expected?).
At Yuga, we use an internal testing map where multiple models can be loaded with different animations applied. This helps us quickly spot issues and fix them before launch.
👉 Following this process will help you avoid surprises later and give you confidence that your avatar is ready to shine in Otherside.
Find the broken BPs containing a Morpheus Array
Re-pick their correct Inner Type.
Re-compile the blueprint and save it.
How to test it?
Make sure Morpheus Arrays are working as expected and all blueprints are compiling fine.



GetInputMappingContextsODK provides an out of the box analytics integration with Mixpanel. You're able to register your own Mixpanel project with Unreal, or reach out to Yuga to be added to their ODK Mixpanel project.
To enable Mixpanel analytics for your world you need to:
Add BP_ODK_Mixpanel_AnalyticsWorldService to your additional world services within your world settings.
Add a mixpanel project token to live config at : project : ODK.Analytics.MixpanelProjectToken
Retrieve the latest `project.schema.json` from the ODK. Accessible here, or within any v8.2 template. Merge this file with the existing project.schema.json that lives at <project_workspace>\Config\LiveConfig\Schemas. If you haven't made any local project edits, you can safely just replace the file, otherwise you should merge the content with your local changes.
Date of change: 16/07/2025
Affected Features: PIE session
What’s broken and why?
The way you login needs to be updated to use our new authentication system
How to fix it?
For any existing project, you'll need to modify your authentication settings. 1. Open to Editor Preferences -> General: Sign In Settings 2. Expand Per Client Sign In Settings 3. Modify each client's sign-in settings to: "Custom" : "https://o7e.dev/api/editor/login" 4. Restart your editor
How to test it?
Start a PIE session and ensure your client can connect to your local deployment.
AddNiagaraEffect returns an Id for the effect that should be used when attempting to remove the visual effect. An example of this can be seen inside BPMC_ODK_GucciTrailHandler::RemoveGucciTrailEffect whereRemoveNiagaraEffect is called with the relevant EffectId.
You can define new wallet views by creating a new child of BP_ODK_WalletView. Then all you need to do is override IsRelevantToken on the wallet view to filter for tokens your view cares about. Here is an example of koda cam photos:
IMPORTANT: You can create your own custom views but should only do so if there is not an existing view that already filters in the same way. Having duplicate filter logic in two views will reduce the efficiancy of the system unnecessarily.
You can use IsTokenDataRelevantToView to check if a token fulfils the filter criteria of a wallet view. In the case below, we check if the token represents ownership of an emote.
You can bind to event on wallet views to be informed when tokens of a particular type change in your wallet. In the case below, we want to be informed whenever koda cam photos have been added or removed from a wallet. This particular event batches adds and removes together so that over a given frame, if there are multiple add and remove token events, the event will only be broadcast once.

MarkContextNeedsUIMode is a wrapper around the BPC_ODK_UIModeComponent component attached to BP_ODK_PlayerControllerBase. The BPC_ODK_UIModeComponent is not repsonible for the effects of UI mode change (showing the cursor for example). It is simply responsible for the maintance of the current UI mode state. BP_ODK_PlayerControllerBase handles the result of UI mode changes here:
BP_ODK_PlayerControllerBase .If you require different logic to be perfomed when the UI mode changes, you can override the functionality is HandleUIModeChangeRequest.

Forces are applied to the BPM_ServerPhysicsObject using the server RPC Server_ApplyImpulse. You can use this to apply a regular impulse and angular impulse to your object (move the object and spin the object).
Inside BPM_Dice, you can see Server_ApplyImpulse is used to apply a random impulse and angular impulse to the dice when the dice actor is interacted with.
BPM_Dice exampleSelfie Cam
The Selfie Cam example is made up of several modular pieces to showcase the various features required for the end-to-end flow.
At a high-level, the system is composed of the following key components and functions:
BPC_SelfieCamComponent
A component attached to the BP_ODK_PlayerCharacterBase, responsible for toggling selfie camera mode. It spawns the BP_SelfieCameraActor.
BP_SelfieCameraActor
This actor contains a CineCamera component. Once spawned, it attaches to the player's hand socket and communicates its location to the Animation Blueprint via a Blueprint Interface. This enables the Animation Blueprint to sync the hand's location using an IK chain. The actor also manages the Camera UI and binds to its events.
BPC_HighResShot
Another component on BP_ODK_PlayerCharacterBase, with a Take Screenshot event that allows specifying location, format, and resolution. It can display an optional preview widget, which is customizable and works with a Blueprint Interface for swapping. The component also includes the following Event Dispatchers: OnHighResShotSaved, OnFlashShown, OnFlashClosed, and OnPreviewShown.
BPFL_ScreenshotTools
After a screenshot is saved, the image can be uploaded to the user collection using the User Collection Upload Screenshot function from the BPFL_ScreenshotTools library.
Important Note: The BP_AttachmentManagerODK singleton must be defined in your level's world settings > Non Morpheus singletons section. The Koda Cam system relies on this manager to perform mesh attachments to both character and crowd actor instances. If the singleton is not present, attached meshes will fail to render, and the avatar selfie stick functionality (for short-arm configurations) will not execute correctly.
The Selfie Cam can be activated using the IMC_SelfieMode Enhanced Input Mapping with the following actions:
IA_ScreenshotCamera_Toggle - Enable/Disable the Selfie Camera
IA_RotateCamera - Rotate the camera when active
IA_ZoomCamera - Changes to field of view to simulate camera zoom
IA_SelfieMode_TakeSnapshot - Save the image to disk
The selfie cam is capable of saving high resolution images to disk and uploading them. This could lead to several large files being uploaded by multiple users. To mitigate any risk, several Live Config values have been added:
ODK.SelfieCam.Enabled - Allows for disabling the activation of the Selfie Cam. If this is set to false, when a user presses the toggle shortcut, the BP_SelfieCamActor will not be spawned. ODK.Collections.AllowImageUpload - Users will stil be able to use the Selfie Cam, but the Image Upload option will be disabled.
In this section we will go through how to properly setup a material in blender such that it exports properly to the GLB.
There is extensive information in the Blender docs about this if you want to read more: https://docs.blender.org/manual/en/4.5/addons/import_export/scene_gltf2.html
The first step we want to take is opening up Preferences
Navigating to Add-ons and then glTF 2.0 format and enabling Shader Editor Add-ons
This will allow us to export occlusion maps if we have them.
Switch to the Shading tab to begin making our first material.
Select a mesh in the viewport and click the New button on the top of the Shader Editor window to create a new Material. I am going to rename this material BaseMaterial.
Let's add a new node glTF Material Output we won't need it immediately but we can get it setup now in case we need it in the future.
Just to test how this works let's make this material chrome like.
Adjust Metallic up to 1.0
Adjust Roughness down to 0.1
Then export the glb, run it through the avatar web tool and set the path in the ODK Unreal Editor to the newly downloaded GLB.
Boxie now has a chrome head!
Any specific material settings should be tested at this point to help inform what values any texture maps you create should follow.
Currently the avatar web tool removes any transparency from materials. If you want to use transparency you'll have to use the CLI tool that we will cover in the pipeline portion.
Transparency is pretty limited in game for avatars. There are two types that are supported.
Dithered - well suited for decals or cutting holes in geometry.
Blended - This can create semi-transparent materials however it does not support reflections so materials like glass are hard to recreate. Additionally there can be some Z-Buffer issues when using this method so if this is enabled the entire object that has this material applied to it should be expected to be transparent.
You can switch between these two settings in the Details panel in the Materials section in the dropdown Render Method
We cover best practices for asset production and QA in Asset Production Best Practices.
For this simple example the final Blender file ended up looking like the images below. All textures, meshes, and materials were made simply so I could finish this guide in a reasonable amount of time.
The QA process you take depends greatly on how your collection is structured. I would recommend taking the approach for review we did in the section and adapting that for your collection. You could:
Create a UI in engine to switch between avatars either via local files or use the load GLTF from URL node and have them uploaded to a public location.
Set the blueprint to cycle through all avatars or a subset of avatars and render that to a video file from unreal to review the avatars.
If working with a large collection it helps to determine what the smallest subset of avatars represent all traits, and then add some spot checking of random avatars to hedge against faulty assumptions in the minimum trait list.
The boneyard is a space that the ODK development team used to develop ODK functionality and serves as a space where we can showcase example content and use cases to the ODK community.
All content within this space should be used as an example for how we have achieved aspects of functionality.
The companion Morpheus Actor of BP_ODK_Example_PlayerCharacter. This Blueprint extends from BPM_ODK_PlayerCharacterBase and should be used for all replication logic within your world.
This Blueprint extends from BP_ODK_PlayerCharacterBase and should be used to add your own character logic.
This Blueprint allows easy extension of the player controller. It should be used when you wish to inherit M2 base functionality while also adding your own logic.
Documentation regarding wallets
Our wallets system allows developers to interact with a player's owned (or delegated) tokens. BP_ODK_WalletWorldService and BPMC_ODK_WalletComponent are the main classes you may need to interact with.
BPMC_ODK_WalletComponent can provide you with the WalletAddress fo the player.
BP_ODK_WalletWorldService can be for most functionality regarding tokens.
For any wallet that's associated with a user's account, we automatically listen for all tokens within the wallet, and store those details within the BP_ODK_WalletWorldService . Any dynamic changes are also tracked at runtime, and you can bind to the following events to respond to token changes:
OnTokenAdded
OnTokenRemoved
OnTokenBalanceUpdated
For more sophisticate token handling, it's recommend that you utilize .
Used to lock features behind definable conditions, often regarding token ownership.
In-world gating uses the base class BPM_GatedAreaBase.
Note: In order to use this in the editor, the blueprint needs access to profile details, therefore, you must be signed in and not in offline mode.
This can be done by opening Editor Preferences > Sign-In Settings and adding your credentials.
This feature intends to allows devs to limit access to gameplay. Players are limited on a condition. This condition usually concerns token ownership, however, the system allows for completely generic gating.
BPM_ODK_GatedArea_Plane is a fully working example that you can use to start gating access to areas of your map. If you plan to create you own type of gated area, we recommend looking at this asset to understand the workings of the system. It has a property GateCondition that can be used to configure who can navigate into an area.
Gating conditions can be added to the gated area actor by configuring the GateCondition property in the details panel of the BPM_ODK_GatedAreaBase.
In the following example, the gated area is gated on whether a player has any token on the ethereum block chain.
There are many types of gate condition that are defined in the ODK. The BP_ODK_GateCondition_AND and BP_ODK_GateCondition_OR are key conditions that allow you to combine conditions together for more complex gating. The following shows a gate condition that requires ownership of both a bored ape and a mutant ape (defined by giving a contract address).
If you can not find the condition you need defined within the ODK, you can create your own condition by deriving a new object from BP_ODK_GateCondition and overriding the IsConditionSatisfied function in your new asset.
The arcade machine is an example of how you can link to an external URL via the web browser. In this example we link to TopTrader.xyz which is done by using the blueprint BP_Interactable_OpenURL and calling OpenWebBrowser with the desired URL on the BP_WebBrowserWorldService
Coins in the Boneyard are examples of how you can implement collectables on chain, these pickups grant players tokens to their wallets.
BP_ODK_PickupExample is an example blueprint where you can see how this is achieved, using the RequestGrantToken node we define a token to grant and the target will be defined by the WalletComponent that will contain the address of the players wallet.
The ODK Quest system builds on top of the Task Flow system to offer a blockchain-integrated questing experience. Each quest is defined by a set of sequential tasks, with a unique on-chain Fungible Token acting as both the activator and the progress tracker.
A quest typically looks like this:
The player is granted a Quest token to begin.
Glyph
Otherside uses a user-friendly wallet called . Glyph simplifies web3 onboarding for both users and developers by using traditional web2 methods of account creation (ie. using a gmail account).
Glyph facilitates login, wallet integration, fiat onramping, and onchain actions with gas sponsorship. Built for developers, it's scalable and easy to integrate.
It's built on Privy technology which allows for common SSO's to be used in the sign up process like Google. More information about the service can be found here:
What do we use Glyph for?
We use Glyph in the Otherside to allows users to seamless create wallets that they can use within the experience, Glyph also allows linking to third party wallets e.g Metamask. Alongside this we also use Privy to manage user accounts within the Otherside Platform.
To create an account, please first reach out to your Yuga support contact, and provide your (and your teams) email address details. We can then credit them with the appropriate permissions. Once complete, please browse to the to create your account.
To create an account, or login. Click the "Login" button, and then enter the email details you provided above. You'll be sent a confirmation code to your email to validate your login.
The ODK notification system allows for developers to display notifications on a players HUD.
By default, the BPM_NotificationsSingletonODK singleton is set in the World Settings. This can be extended and replaced to allow for custom functionality.
The singleton serves as a notifications manager, broadcasting notifications upon receiving a request.
Documentation for ODK Video/Millicast Screens
The video screens in the ODK are intended to be a seamless solution for playing video alongside important millicast streams. The BPM_VideoPlayerWithMillicastBase actor is a base class for these screens but will not function without a mesh to display the video on.
BPM_VideoPlayerWithMillicastCurvedScreen is an example of how the base class can be extended to show the stream on a mesh in the world.
The important function to consider here is “GetMeshComponentToDisplayOn” and should be overwritten to define a static mesh component and MaterialIDs
In this example, the video is displayed on the curved video screen mesh and assigned to material IDs 0 and 1. This allows for the image to be shown the correct orientation on the back side of the mesh.
The overlay is a fully featured Chromium browser that is used in the ODK to render UI elements, this allows us to get all the benefits of web based development and also have a unifying set of menus across all ODK experiences.
Web Browser functionality is controlled via the BPFL_ODK_WebBrowser function library, and the BP_WebBrowserWorldService .
Pressing "Tab" within an experience will open the Player Profile overlay menu, which is available in every experience within the Otherside Platform. This overlay displays the Players progress and inventory as they explore the Otherside. This page consists of the following sub pages:
Overview
This section gives the player a high level view of their progress within the Otherside, it will track key metrics, show their avatar, display badges and allow them to show off their prized NFT's on this page.
Documentation regarding persistence in the ODK
The persistence system in the ODK can be used to store data remotely. Examples of it's use might be: storing player progression or storing high scores for a mini game. On the backend, the storage system is simply a map of keys and values as string. You could imagine that for the high scores example, you might want to store the data using "high_scores" as a key and a json encoded object string for the data in this form.
The main class to be concerned with is BP_ODK_PersistenceWorldService. This world service provides functions in the following flavours: ReadValue, RegisterInterest and Save. The full list is:
- ReadIntValue
- ReadJsonValue
- ReadStringValue
- RegisterIntValueInterest
- RegisterJsonValueInterest
- RegisterStringValueInterest
- SaveIntToPersistence
- SaveJsonToPersistence
- SaveStringToPersistence
Documentation regarding ODK interactions
The ODK interaction system facilitates interactions between the player character and in world props.
Player characters have the BPC_ODK_InteractionComponent attached to them. All interactable actors have a BPC_ODK_InteractableComponent attached to them. The BPC_ODK_InteractionComponent periodically polls interactable components to see which is the current
Each task completion earns them an additional token.
When the player holds the full set, the quest is complete.
These tokens act as stateful markers for player progress, making it easy to track and synchronize quests across game sessions and platforms.
ODK uses Fungible Tokens for quests, meaning players can hold more than one. Token balance reflects quest progress:
1 token: Quest activated
2+ tokens: Tasks completed
X tokens: Quest finished (X = tasks + 1)
The token balance drives the task flow, ensuring only the correct task is active at each stage.
Quests are created and managed via the Creator Portal.
This allows you to:
Define quest metadata (name, image, description, group, etc.)
Add tasks and rewards
Mint and grant tokens to test users
Once a quest token is minted, its Token ID (formatted like 33139:0xABC...:1) can be used in Unreal to connect the flow logic.
In Unreal, each quest is implemented using a BP_ODK_TaskFlow actor and a data asset derived from PDA_ODK_TaskFlow. Tasks should use BP_ODKTaskFlow_QuestTaskBase to ensure:
The task sends the next token upon completion
The system waits for the new balance before progressing
This ensures the task flow is always aligned with the player’s on-chain token state.
Quests rely on the TokenHandlers map in the player’s BPMC_ODK_WalletComponent. These allow the game to respond to token changes (e.g. showing a notification, starting a flow, granting a reward).
By default, Quest and Achievement handlers are included, and you can extend or replace these for custom behavior.
When a quest is completed (i.e. all tokens received), additional reward tokens can be granted. These should be defined in the quest’s metadata and triggered via the BP_TaskFlowAction_GrantAchievementFromAttachedToken executor on the final task.
You can activate a quest by granting the initial token. This can be done:
Via Blueprint (BPFL_ODK_WalletHelpers)
From a UI panel or NPC interaction
From a LiveConfig-driven quest browser
BPC_ODK_InteractableComponentsBPC_ODK_InteractionComponentBPC_ODK_InteractableComponentIf you want to set up a new prop actor to have interactions, the simplest method is to attach a BPC_ODK_InteractableComponent_WidgetPopup to your actor. Once this is done, select the component in the "Viewport" and move it to an appropriate position on your actor. This should be a location where you expect the camera to be looking when the player interacts with the object.
Now, you can hook into events on the component to execute your behaviour:
- OnInteract: Execute your custom interaction logic
- OnFocused: Marks the interactable component as the active target, allowing it to receive interaction input when the player presses the designated key.
- OnUnfocused: Removes the component as the active target, preventing it from receiving interaction input.
Lastly, we can configure the BPC_ODK_InteractableComponent_WidgetPopup properties:
- Interaction Distance: the distance within which the player character needs to be to interact with this component.
- Priority: If multiple interactables are able to be interacted with at one time, the highest priority interactable will take precedence.
- Widget Transform: Allows you to configure where the popup widget will be displayed in relation to your actor. We recommend setting the widget to sit above your actor























The Task Flow system allows you to define a sequence of tasks that guide a player through an experience—such as a quest, tutorial, or onboarding sequence—using a structured flow of triggers, conditions, and actions. Each task is handled one at a time, and the player must complete them in order to progress.
The core components of the system are:
A trigger defines when the task flow should begin. For example, the flow might start when:
A player receives an on-chain token
A player overlaps a specific volume in the world
The game reaches a certain boot state
Triggers often work with conditions to check whether the flow is allowed to start. For example, the trigger might fire when a player enters a zone, but the condition ensures it’s only valid on Wednesdays.
Conditions exist at both the flow level (whether the sequence should begin at all) and the task level (whether a specific task is ready to activate).
These conditions are logic blocks you can define—for example:
“The player has not completed this flow before”
“It is daytime in the game world”
“The player has reached a specific location”
Conditions must complete successfully for their corresponding flow or task to proceed.
A task defines a goal that the player must accomplish to move forward in the flow. For example:
Task 1: Jump three times (track jump count from a movement mode change)
Task 2: Perform a specific emote
Each task must signal when it's complete using the Task Trigger Execution Complete event, which tells the Task Flow system to move to the next step.
All tasks are initialized using the Initialize Task Flow Executor event.
Actions are optional, fire-and-forget logic blocks that execute at key moments in the flow lifecycle. You can use them to:
Show a VFX or sound at task start
Spawn an item when a task begins
Hide UI or clean up actors when a task ends
Actions can fire on events like:
Flow Start / Complete
Task Start / Complete / Skipped
They are lightweight and don’t report back to the task system—they simply execute and move on.
This system is designed to be flexible, data-driven, and extendable—whether you’re guiding the player through an onboarding sequence, building a narrative quest chain, or triggering progression gates.
As a brief reminder your organization will contain all of your experiences. You may have multiple projects in your organization corresponding to different experiences. An example might be you have a concert experience and shooter game experience as part of your organization. In a given project, you may have multiple worlds. This might be a different world for a variety of maps in a shooter game.
The RegisterInterest and ReadValue functions have callback params to let you know when the data has been recieved. In the case of ReadValue callbacks, they contain a "Value" parameter and a "Success" parameter. The key may not exists in the database in which "Success" will be false. RegisterInterest will not immediately get the value for you. Instead it will listen for any future updates to the value and inform you when it changes by calling the callback you provided.
Different key value stores are used for different users. This means that you will only be able to access a player's data on the auth client. Similarly, the server has it's own store which can not be accessed by clients.
This allows for the same key to be used across multiple stores. That is to say that multiple users and the server can all use the same key if so wished.
"high_scores" : "{"simon": 10, "david": 3}"Metaverse Markup Language (MML) - Official Site: Open-source web technologies for building multi-user metaverse experiences using HTML and JavaScript.
MML Documentation: Comprehensive guides and references for MML elements.
MML Discord: Join the community on Discord to discuss MML and connect with other developers.
Main MML Repository: Metaverse markup language for describing 3D multi-user interactive metaversal objects and experiences based on HTML.
MML Editor: An interactive editor for building and testing MML documents.
3D Web Experience: Packages to run web-based, multi-user 3D web experiences supporting MML.
Esbuild Plugin MML: An esbuild plugin that bundles JavaScript/React sources into HTML documents for MML.
MML React Starter Project: Example of a server that serves a live MML document using React.
: Example server for serving live MML documents via WebSocket.
: Implements an MML Guided Tour with live, multiplayer experiences.
: Uses MML, 3D Web Experience, and React for interactive 3D web experiences.
: Minimal 3D playground powered by MML for creating live, multiplayer experiences.
: UI overlay for quick access to MML resources and project sharing.
MML Blog: Stay updated with the latest news, tutorials, and announcements related to MML.
MSquared Blog: News and updates from MSquared, with frequent MML-related updates.
Introducing Crowd Support in Web Worlds: Mass concurrency and Crowd Support in MML.
AI-Powered NPC with MML: Guide on integrating AI to power NPCs using MML and OpenAI.
: A live, searchable index of creators, assets, and activity across worlds and chains, with trusted snapshots and runtime policy enforcement to power interoperable MML experiences.
: Fan, engagement, and loyalty platform with MML support.
MML Examples: Demonstrations of MML capabilities and project templates.
MML Editor Explore: Explore examples and code snippets within the MML Editor.
3D Web Experience Examples: Examples showcasing the 3D Web Experience with MML by TheCodeTherapy.
Crazy Run 2: Obstacle course game with leaderboard and achievements, created entirely in MML.
Live Streaming to the Metaverse: How to stream events inside of MML using Cloudflare.
Cloudflare Stream: Service for hosting streams integrated with MML.
Dolby OptiView: High-quality live streaming and playback service.
OBS (Open Broadcaster Software): Free and open-source software for video recording and streaming, compatible with MML setups.
Somnia Playground: A hub for creativity and immersive experiences using MML.
Somnia Blockchain: High-speed EVM blockchain with native MML support.
Etherbase: A backend read/write service for EVM contracts that makes it easy to emit events, set state, and execute functions. Designed to be MML-compatible for wiring live on-chain data into MML experiences.
GLTF Avatar Exporter: Tool for exporting avatars in GLTF format for use in MML.
MML Avatar Tools: Web tool for fixing mesh, skeleton, and material issues in avatars.
MML Blender Extension: Blender extension for MML avatar preparation.
Sandbox Converter App: Web application that converts various types of The Sandbox avatars into MML-wrapped GLBs, producing assets ready to drop into MML scenes.
: React component library for building fully-rigged 3D avatars powered by PlayCanvas, plus a minimal Next.js app as a live playground/reference integration—useful in MML pipelines.
: Tools for preparing game-ready rigs in Blender.
: Blender addon for optimized rigging.
: Comprehensive guide and tutorial for creating MML avatars.
: NFT collection with MML-ready avatars.
: MML Conversion of Mcbess Celmates avatar.
: MML Conversion of Broadside avatar.
: Another Broadside avatar conversion in MML.
: MML Conversion of Sappy Seals avatar.
: A quick guide and tool to get started with MML avatars at scale.
: Batch web tool to generate multiple MML files from GLB URLs.
: VRM to MML avatar conversion.
: VRM to MML Avatar conversion.
: Online tool for testing and creating avatars.
: Additional resources for creating avatars with MML.
: Detailed guide for creating MML avatars using Blender and free rigging tools.
: A detailed reference for M-Character elements in MML.
: A video tutorial covering avatar creation techniques.
To access and manage your Glyph account, click on the profile button in the top-right hand corner.
Glyph automatically creates a wallet for you when your account is created. We use this wallet for all ODK experience token granting. If you are awarded tokens from participating within ODK events, they'll be credited to your privy wallet. You can also link additional wallets to your privy account, using the "Add a wallet" button. This is required for the WebApp and Unreal experiences to read which tokens you have ownership over and credit you with the appropriate entitlements that are attributed to those tokens. For instance, if you want to be able to switch to your BAYC (or any Yuga) avatar in build, you'll need to link to your wallet that contains the BAYC token.
For more on how we interface with your wallet within experience, please see Wallets.
In order to display notifications on-screen, you must implement a notifications widget on your WBP_HUD. An example of this is the WBP_NotificationsDisplayODK widget which contains a vertical box for containing the notification widgets and the functionality to bind to the singleton broadcasts and then add the notifications to the container.
The example widget allows for custom notifcation widgets, but assumes that these widgets extend from WBP_ODKNotificationsBase.
WBP_ODKNotificationsBase is intended to be used alongside WBP_NotificationsDisplayODK to create custom widgets for notifications. This base class contains functions for setting up the custom widget and helper functions for optionally downloading the notification images.
The SetupNotification event should be overidden to set textures and text from the notification payload.
Notifications can be sent using the helper functions in BPFL_ODKNotifications.
This function sends a notification to the authoritative player with the following payload:
This allows for grouping of notifications. For example: If you have multiple notifications of the same type, such as collecting an item, any new notifications will replace the existing notification in the list to avoid notification spamming.
How long to display the notification on-screen if it is not dismissed by the user.
Contains information relevant to the notification such as a Title, Content text, image etc.
The struct also contains an Additional Data Morpheus packed struct object which allows for including any information not covered by the struct. It is then up to the developer to unpack this object and use the data as they wish.
This node takes in the same parameters as the local notification, but will be displayed on all users screens.

The base class contains several configurable variables to help with the setup of the videos and millicast.
The Receiver name will be used on the Millicast Control panels to set which screen to display millicast content on.
The media texture and media player variables can be reused across multiple screens to keep them in sync, or a unique media player/texture can be created if the user would like media to continue uninterrupted while millicast streams are played on other screens.
The playlist variable contains a list of M2FileMediaSource assets that reference local video files. The player will loop through the playlist until interrupted by a live Millicast stream, at which point it will switch to the millicast stream texture. When the stream ends, the screen will switch back to the playlist and continue.
Clear the sign in credentials by going Tools > Clear Credentials
After doing this step you can try to relogin with the editor, if this still fails then redo Step 2 and proceed to Step 3.
Clear your web session by deleting the webcache folders from project/Saved Folder
After doing this Restart the Unreal Editor if the issue persists please move ontpo Step 4
Close the Unreal editor and open Windows Credentials Manager
Select WIndows Credentials
Delete any credentials starting with Improbable, M2 or Morpheus
Reopen the Unreal Editor and you should be prompted to re sign in, which should resolve your issue

This is where players can change their avatar from, it will display all avatars that are available within their collection, its worth noting that this functionality will not work if players are forced to render a specific avatar for a experience.
Inventory
The inventory consist of all tokens that the player has earned as they voyage through the Otherside, this will consist of Avatars, Emotes, Badges, Items and any other tokens that they have gathered.
Quests
Quests can be triggered within experiences, and once a quest is start it will automatically be added to this section.
Badges
Badges are rewards players can get by achieving certain things within Otherside
Travel
The travel tab allows players to navigate around the Otherside universe
For information on how to use the web browser to open links to your web pages, see the section on Boneyard Example Content here.



IsConditionSatisfied to check whether a player has any token from an array of configurable NFT Chins.Please use this guide for instruction of how and when to access the preview stream.
The ODK Launcher has two streams which are:
Public: This version of the ODK is released to all developers.
Preview: This version we grant early access to new ODK features for early testing. Once the preview version has been publically released you will need to switch back to Public and we will remove you from the Preview stream. If you need to access a preview version of the ODK you will need to reach out to your Yuga Representative who can organise this. Developers need to be given special access permissions to use this stream.
If you are switching to Preview then please follow this guide for how to do so.
Guide
1. Log into the Launch via Otherside Login, and NOT Msquared Login
2. Select ODK Alpha from the web project drop down
3. Click the settings cog in the top right and select Preview in the update channel drop down
4. Click the Top left button that loads the launcher version installer menu
5. Click Add Installation
6. From the version dropdown select ODK Chapter 8.1 Preview and click Install
Documentation for ODK input component
BPC_ODK_InputComponent is the main manager of input in the ODK. This component lives on the BP_ODK_PlayerControllerBase asset.
Enabling and disabling IMCs
BPC_ODK_InputComponent can be used to control which IMCs are active. BPC_ODK_InputComponent uses a stack of states where each state contains a set of IMCs. When a state is active, all enabled IMCs are disabled and all IMCs on the newly enalbed state are enabled.
PushInputMappingContextState - can be used to add an new state to the stack. This state will become active.
RemoveInputMappingContextFromState - can be used remove a state from the stack. The previous state in the stack will become active.
AddInputMappingContextToState - can be used to add an additional IMC to an existing state. If the state is active, the IMC will be enabled.
RemoveInputMappingContextFromState - can be used to remove an IMC to an existing state. If the state is active, the IMC will be disabled.
Getting the last input type
BPC_ODK_InputComponent can be used to get the last input state (Gamepad/Keyboard) by calling GetLastInputType or hooking into the event OnInputTypeUpdated.
The scoreboard shows the number of kills and deaths for each player. The scoreboard updated whenever: the scoreboard UI is opened, a player joins a team, a player is killed and whenever a player joins/leaves the game. Use the "Z" key to open the UI.
All functionality can be found in WBP_Scoreboard.
Spawn point zones are added to the map for each team. When a team is selected, the character will spawn at the associated teams spawn point.
AJ_PlayerStartVolumes have been placed around the map and tagged with either: RED, BLUE or GREEN. On BeginPlay, BPM_ODK_ExampleCombatCharacter caches all player start volumes on auth clients. When BPM_ODK_ExampleCombatCharacter is respawned, we use our team to find the appropriate player start volumes and teleport to a random player start in the player start volumes.
Since this collection doesn't actually exists we are going to make it ourselves. Feel free to skip this portion and just grab the trait metadata from the resources .
The Boxie collection only has three different traits categories:
Head
classic box
sphere
NOTE: The creator portal is in Alpha and only avaliable to specific developers, please talk to your Yuga representative if you have a need to access this.
The ODK Token System provides a unified way to track and synchronize player progress, rewards, achievements, and quests across the blockchain and experience.
Tokens can be used for persistent state, with each token type having a specific purpose and behavior.
In the context of ODK, a token is an on-chain data object that represents something the player owns.
Token are:
Documentation regarding character movement in the ODK
The ODK seperates different types of movements into different conceptual movement modes. Walking, grinding and gliding are examples of different movement modes available in the base ODK.
Movement modes are controlled via a component on the BP_ODK_PlayerCharacterBase render target. By default, this component is BPC_ODK_MovementComponent, however this can be overriden on BP_ODK_PlayerCharacterBase by setting the MovementComponentClass property.
The BPC_ODK_MovementComponent controls which movement mode should be active at a given time. A mapping is defined on this component between a "movement mode id" and a movement mode component class. This allows for easy overriding and addition of movement mode behaivours.
Adding a new configurable input action
To enable configurable keybindings for an input action, you should configure the following on the InputAction asset.
You first may want to add the IM_MappingGroup modifier. This defines what context the input action can be used. This is used to automatically reset deuplicate input action keys that are in the same mapping group. As an example, the jump and move forward input action have been added to the same mapping group so that if the user can not add the same key for both.
You then will need to add PlayerMappableKeySettings to the input action's user settings. The following should be configured: Name - just the name of your input action asset. Display Name - The text to be shown to the player in UI. Display Category - The category your input action will be put under in the keybindings UI.
Lastly, you will need to configure you input action inside whatever IMCs it is added to. You should probably add keys for both keyboard and the gamepad. The following should be configured: Name - This should be the name of the input action asset appeneded with either "Keyboard" or "Gamepad" respectively. Additionally, if your input action needs to be added to multiple IMCs, due to the fact that the "Name" property must be unique accross the project, you can append a different number to the end of the name in each IMC.
Multi-objective Daily Tasks
19 New Daily Tasks
7 KodaCam Daily Tasks
Map function
Replay the 'A Spark Reborn' any time from the World Travel overlay
Challenge Leaderboard Reset
Added 'Show Player Names' toggle Graphics Settings menu
Content Updates
Rebalanced chest speeds for all chest chaser Challenges
Profile overlay is now bound to “P” (previously Tab)
Map is bound to Tab
“70s dance” renamed to “Ritualistic Dance”
Descriptive Tags added Challenge Entry Portals so players can understand what the challenge contains
Reoriented the Compass so the Koda Temple is North from the starting area
Reduced chance of getting the same Daily Tasks multiple days in a row
Reworded 'A Spark Reborn' initial tasks to make them easier to understand
Bug Fixes
Fixed an XP exploit related to World travel
Challenge Entry portals sometimes didn't appear as completed when they were
Removed broken textures on Challenge Entry Portals
Improved collision on Swamp train
Made it clearer that Daily tasks don’t award XP at max level
Corrected grammatical issues in Daily Tasks & Badges
Fixed issue with Badge names being truncated when awarded
Fixed issues with Daily Tasks failing to claim
Fixed issues with Badges sometimes failing to award
Fixed getting stuck in 'A Spark Reborn' when chain is down
Fixed Server Crash in Morpheus Array
Removed invisible gems in gem collection Challenges
Collision improvements across all Challenges
Fixed soccer ball not appearing when a soccer event is active
Fixed 'A Spark Reborn' occasionally awarding double XP
Fixed Bubble deeplinks
Re-enabled trails on soccer ball
Avatar clipping with assets in Gaming room
Grounded floating grass outside the Crystal Caves
Updated collision around Nexus spawn point
Fixed visual distortion on the black hole in Fortune’s Rise IV
Fixed issue with users getting stuck in Challenge chests
Fixed visual distortion when viewing grind rails from certain angles
Fixed visual distortion when viewing Challenge entry portals from certain angles
Fixed performance micro spikes during normal gameplay
Dress Code avatar system for Clubhouse
This should prevent non-Ape and MAYC avatars from appearing in Clubhouse
Free Bubble mode (Walkie Talkie)
User can now talk to each other without constraints of Bubble
Still uses Bubble system
Listen Mode - Users can now listen to any Public bubble without joining
Bubble List improvements and fixes
Resolved duplicate Bubbles appearing in Browse Bubbles
Resolved duplicate user entries
Placeholder name tags (different to privy id issue)
Scrolling improvements
Correct profile picture should appear
Fix for Bubbles UI being greyed out if User is kicked
Bubble UI and buttons have received some TLC polish updates
Bubbles are now sorted by attendance in Browse Bubbles menu
Bubbles should no longer appear with jagged edges
Bubbles notifications will now appear on far right of screen if Bubbles menu is closed
Other notable fixes:
Launch pads should no longer put User in somersault animation
Fix to volume sliders in Settings menu
Hotfix follow-up.
Updated skybox with islands
Change to make gliding easier when jumping from the ground / low rocks
Fix for white characters on low graphics settings
Fix for solid cylinder volumes for co-op emotes on low settings
Black screen on initial loading of GFN reduced
Bubbles UI getting overlapped by host disconnect message
Users are now unable to interact with each other in Bubbles with F
This update finishes the migration of existing worlds to UE 5.5. The focus was addressing community feedback around UI improvements and environment upgrades.
Updated Social Spaces to UE 5.5 and ODK v9.1
Reworked Settings menu for better responsiveness
Updated Settings UI to match ODK
Fixed Broken bubbles character animation due to UE 5.5 update
Fixed Bubbles mesh not appearing as soon as Bubble is created due to UE 5.5 update
Fixed Players aren't falling when they leave Bubble or when Bubble is popped due to UE 5.5 update
Reworked Tokengating system (new version working for Clubhouse)
Add Bubble collisions volume around clubhouse to prevent Bubbles overlapping token gate area
Upgraded Swamp Train route and added with functional trains
Revised Train Stops
Fixed lighting for Medium graphics setting
Added Live Config option to allow 'F to interact' in Bubbles
See Bubble User count in participant list
Disabled current SFX in Bubbles UI when changing Movement Mode
Improved Medium graphic Shadow performance
Re-added Bubble Backflip emote
Set default movement speed for players to Sprint
[Platform Web update - not content] GFN Disconnect fix


BPM_ODK_ExampleCombatCharacter 
BPM_ODK_ExampleCombatCharacter 









smiley face
Upper
classic red
classic blue
classic purple
metallic red
metallic blue
metallic purple
polka dots
stripes
Lower
dark
light
Let's make a collection of 100 boxies. This is going to be a very rudimentary collection generation in python.
You can run this script directly in Blender by:
Changing the view to the scripting tab
Create a new text block by clicking the New Button
Paste the script above into the text block, you can change the variable OUTPUT_FILE to a location that makes sense for you, like in a folder for this project. Then press the run button.
This should save out a JSON file that is structured like an NFT collection metadata.
Minted and granted via the Creator Portal
Stored in the player’s wallet
Used to trigger or gate gameplay systems
Synced in real-time between web, blockchain, and in-game logic
All tokens follow a consistent format for their Token ID:
Example:
33139
Chain ID (e.g. APECHAIN)
0x...
Contract address for your experience
3
Unique Token ID
Each token is defined by its type field in metadata, which determines how it is handled in-game. This type is also used by the TokenHandlers map on the player wallet to route token logic.
quest
Sequential multi-task experiences with reward support (See: )
achievement / badge
Recognitions of completion, progress, or milestones
image
Selfie/image tokens — used for storing screenshots or custom UGC
item
Inventory items (experimental or project-specific)
custom
Any developer-defined use case (requires custom TokenHandler)
Each type can have its own UI, reward logic, and flow triggers.
Tokens can:
Start quest flows (by triggering BP_ODK_TaskFlow)
Unlock achievements or badges
Be displayed in user profiles or overlays
Gate access to in-game systems or areas
Be granted via Blueprint, server-side logic, or web interfaces
📜 Quest Tokens – Multi-step task sequences with XP and rewards
🏆 Achievement Tokens – One-time accomplishments or stats
🎖️ Badge Tokens – Visual trophies or collectibles
🖼️ Image Tokens – Player-submitted images (e.g. selfies, UGC)
⚙️ Custom Token Types – Extending the system for new behaviors
Initial Movement ModeDefault Movement ModeStartup Movement ModesEach movement mode is wrapped up in it's own component that derives from BPC_ODK_MovementModeComponentBase.
BPC_ODK_MovementModeComponentBase components as a good general rule should manage these areas:
Input - Handling enhanced input actions that modify the behaivour within a movement mode.
Character movement - The logic that actually moves the character.
Movement mode validity - Whether it is appropriate to be able to enter the movement mode. This is comunicated through the CanEnterToMovementMode function.
Ending the movement mode - Ensuring the movement mode is ended when appropriate.
We will now consider the BPC_ODK_GrindingMovementMode as an example.
Here we can see how input is processed in the grinding movement mode. The only valid input action on a grinding rail is "Jump". First, we override the BPC_ODK_MovementModeComponentBase base function AddInputBindings. Inside this function we can call AddInputAction for each input we want to process. We can bind to the IA_ODK_Jump input action listening to the "Triggered" event and setup our callback. Inside the callback we perform the appropriate logic. In this case stopping the grinding and making the character jump. You can see that the callback has a "Value" paramater that is sometimes useful when processing input. You should use the value corresponding to the "Value Type" on the given input action.
Ending the movement mode can be done by simply requesting a new movement mode. Inside the BPC_ODK_GrindingMovementMode StopGrinding function, TrySetDefaultMovementMode is called on the ODK movement component. We could transition to any movement mode we liked at this point but recommend going back to a the default movement mode.




ODK Base UI is a flexible UI framework designed for building reusable and dynamic widget controls. It simplifies logic binding, value syncing, and visibility control by pairing each control with an Executor.
Controls are placed into UI layouts like any other widgets, but their functionality is entirely data-driven via executors.
This keeps consistency thoughout the Otherside platform, allowing for devs to reuse existing designed controls.
Each control can be paired with:
A Value Executor (handles functionality and state)
An optional Visibility Executor (handles when to show/hide the control)
An optional Registered Control Name (for simple runtime access via the UI system)
The Executor is a Blueprint class that drives the behaviour of a control. Every executor supports the following functions:
This system makes each control self-contained and keeps your UI logic clean and reusable.
A Visibility Executor is a separate class that defines when a control should be visible.
It implements:
GetVisibility → Returns a visibility state (Visible, Collapsed, etc.)
Use cases include:
Hiding the player roles dropdown unless the user is an admin
Only showing Gamepad settings when a controller is connected.
Every control has an optional RegisteredControlName.
This name:
Registers the control with the BP_WorldService_ODKUI
Allows it to be found or replaced dynamically at runtime (See function library below)
The Blueprint Function Library BPFL_ODKBaseUI gives you helpers for modifying UI areas at runtime.
Notable functions:
This system is especially useful in:
Hot-swapping controls during a session
Extending shared base widgets without making duplicates
Control Type: Dropdown
Executor: BP_SettingsExecutor_PlayerRoles
GetCurrentValue → returns a string array of available roles
The Unreal side of an ODK Quest handles how the player interacts with and completes tasks, and ensures that quest progress reflects the player's on-chain token balance. The system uses BP_ODK_TaskFlow actors, Quest-specific task executors, and wallet integration to create a fully synced experience.
For the creation of the Quest token see Creating a Quest token
BP_ODK_TaskFlow Actor to the Level and create a Task PDAThe outlines exactly how to set this up
In your new PDA_ODK_TaskFlow:
Add a new item to the Tasks array
Derive each Complete Task Triggersfrom BP_ODKTaskFlow_QuestTaskBase(described below) — these define what in-game conditions complete the task (e.g. jumping, running)
BP_ODKTaskFlow_QuestTaskBaseFor quests, each task must derive from BP_ODKTaskFlow_QuestTaskBase.
This subclass is essential because:
It handles sending the token to the user when a task is completed
Unlike standard tasks, it does not automatically progress
Instead, it waits for the player to receive the token, then:
Reinitializes the flow trigger
This means:
Token ownership becomes the single source of truth for progress
You don’t risk out-of-sync task flow states
Tasks remain paused until the player actually holds the correct number of tokens
All derived tasks should still call:
...once the task’s completion criteria has been met.
To associate tasks with the correct Token ID, you need a way to retrieve the ID. This can be done per task — but that quickly becomes repetitive.
Instead, use the built-in system based on BP_GetTokenIDBase, which has two helpful options:
BP_GetTokenIDFromCustomDetails
BP_GetTokenIDFromLiveConfig
Recommended: BP_GetTokenIDFromCustomDetails
Each task checks the custom details of the Data Asset
You define a single source of truth inside the asset, and all tasks reference it
This means less setup repetition and centralized management.
Alternate: BP_GetTokenIDFromLiveConfig
Allows dynamically retrieving token IDs from a live config
Useful for centralized overrides or managing IDs per environment
Quests are only activated when the player receives the first token.
This should be granted as part of your experience — how you do this is project-specific.
The easiest way is via:
In the ODK sample, tokens are granted by Interacting with an NPC. This opens a web overlay with available quests.
If your Quest Token includes a rewards array (tokens to grant upon completion), you can automate this via:
We recommend this is assigned to the TaskCompleteActions of the final task, but could also be assigned to FlowFinishedActions.
Why? Because technically the flow never completes in the traditional sense — it’s driven by token sync, and the player could hold the final token before the system registers a full flow completion event. Adding it to the final task will speed up execution.
This approach ensures:
Rewards are reliably granted
The grant is tied to the token logic, not just the internal task flow state
By default, the player's BPMC_ODK_WalletComponent, found on BPM_ODK_PlayerCharacterBase, contains a TokenHandlers map.
This map listens for changes to tokens and routes behavior accordingly.
Out of the box, the wallet includes:
"achievement" handler
"quest" handler
The map’s key must match the type field in the token metadata — for quests, this is "quest".
When a token is added, removed, or updated:
The appropriate handler is triggered
You can use this to:
Display UI
Fire off effects
If you need additional behaviour, you can subclass the default handler and override its logic.
Emotes allow players to express themselves in game with a various animations.
When interacting with emotes in the ODK, the main asset to interact with is the BPMC_ODK_EmotesComponent. This is a component on the ODK morpheus player character BPM_ODK_PlayerCharacterBase.
You can define an emote by creating a new PDA_ODK_Emote asset.
Name - Display name used in UI Emote Id - Used to help unlock emotes via token ownership. The token metadata will specify the emote id that it represents ownership of. Icon - Icon used in UI Type - An enum that lets code categorise emotes. Currently the types we support are: STANDARD and COOP. Emote Executor - An object that runs logic to start the emote
Currently, emotes come in two flavours: standard emotes and coop emotes. Stardard emotes simply play an animation on you player character. Coop emotes allow you to play animations synced with another player character. The way these are handled are very different. The emote executor allows for completely different behaivours to be executed by different emotes whilst being handled the same way in the reset of our logic.
The emotes available to a player in your game can be configured on the BPC_ODK_EmotesComponent on your player render target actor. You can configure the DefaultEmoteSelection property on the component by selecting the PDA_ODK_Emote assets you desire.
PlayEmote and CancelCurrentEmote can be used to start and stop emotes regardless of whether they are standard or coop emotes.
You can override what emotes are available by using the override functions: OverrideEmoteSelection, RemoveEmoteSelectionOverride and, RemoveAllEmoteOverrides. This can be useful for limiting what emotes are available in different contexts. You might only want a couple emotes available whilst the player is in a tutorial but allow all emotes to be played after the tutorial is completed. This can help achieve that. When you apply an emote selection override, a new state will be added to a stack containing the override emotes. When you no longer want to override the emote selection, removing the override will remove the state from the stack returning you to the original emote selection.
You can use GetFilteredEmotes, to get a filtered list of emotes that currently playable. This would take into account emote selection overrides. Filtering can be used in conjunction with overrides. You could for example filter the current emote selection for all coop emotes and then apply an emote selection override to only include those coop emotes.
The Meebit template comes with an example implementation of teams. Players can join different teams to shoot players on different teams.
A player's teams is stored on a morpheus actor component: BPMC_TeamComponent. This component has a client auth replicated property Team that handles replicated of state to other clients.
Teams are defined in the E_Team enumeration. Data regarding teams can be configured in DT_TeamData which uses the S_TeamData structure. This strucure allows you to configure a display name shown to players and a color for a given team.
Adding a team is easy! Simply add a new enumeration value to E_Team betweem NONE and INVALID values. Then configure a new data table entry in DT_TeamData. Lastly, you will need to update some simple helper functions so that blueprint logic knows how to access/use your new team. Inside BPFL_TeamHelpers you will to update GetTeamIndex and GetTeamName, adding values. Once you have added your new team!

The BPC_Scannable component extends the Selfie Cam system by allowing specific actors in the world to be "scanned" whenever a screenshot is taken. This makes it possible to tag screenshots with contextual metadata about visible objects — such as quest items, characters, or interactables.
import random
import json
SEED = 92310
OUTPUT_FILE = "boxie_metadata.json"
"""
List of tuples where the first item is the name of the trait
and the second item is the total supply of that trait.
"""
head = [
("classic box", 60),
("sphere", 30),
("smiley face", 10)
]
upper = [
("classic red", 18),
("classic blue", 18),
("classic purple", 18),
("metallic red", 12),
("metallic blue", 12),
("metallic purple", 12),
("polka dots", 6),
("stripes", 4),
]
lower = [
("dark", 60),
("light", 40)
]
random.seed(SEED)
def populate_list(trait_list):
"""
Create a list of the full population of traits
"""
populated_list = []
for trait_name, trait_count in trait_list:
populated_list.extend([trait_name for _ in range(trait_count)])
return populated_list
# populate all lists and shuffle them
head_populated = populate_list(head)
random.shuffle(head_populated)
upper_populated = populate_list(upper)
random.shuffle(upper_populated)
lower_populated = populate_list(lower)
random.shuffle(lower_populated)
collection = []
for token_id in range(100):
token_data = {
"id": token_id,
"name": f"Boxie {token_id}",
"image": "https://fake-collection-api.io/images/token_id.jpg",
"mml": "https://fake-collection-api.io/mml/token_id.jpg",
"attributes": [
{"value": head_populated[token_id], "trait_type": "head"},
{"value": upper_populated[token_id], "trait_type": "upper"},
{"value": lower_populated[token_id], "trait_type": "lower"},
],
}
collection.append(token_data)
with open(OUTPUT_FILE, "w") as f:
json.dump(collection, f, indent=4)
<ChainID>:<ContractAddress>:<TokenID>33139:0x1122334455667788991122334455667788991100:3HandleValueUpdate → sets the players new roleVisibility Executor: BP_ODK_UIControlVisibility_UserHasSpecificRole
Initialise
Called on setup. Used for binding events or caching references, much like the Begin Play event.
GetCurrentValue
Returns the control’s current state (e.g. current volume or mouse sensitivity).
HandleValueUpdate
Called when the user changes the control through the UI. Developers should implement functionality here (e.g. setting the mouse sensitivity to the current slider value).
ODKBaseUI_GetRegisteredAreaByName
Find a control or UI area by searching for the Registered Name
ODKBaseUI_ReplaceWidget
Replace a widget or control at runtime
ODKBaseUI_ReplaceWidgetByClass
Find a widget that matches the supplied class and replace it with a newly contructed widget.
ODKBaseUI_AddControlToRegisteredArea
Finds an area by name and adds a newly constructed widget to it.
ODKBaseUI_AddTabToNamedTabList
Add tabs dynamically to a tab group


E_Team enumeration
DT_TeamData data table
GetTeamIndex and GetTeamName helper functions










Uses the user’s token balance as the task index
Trigger game logic
When the BPC_Scannable component is attached to an actor, the Selfie Cam performs the following checks:
Bounding Box Generation 📦
The component collects the actor’s visible primitives (Static Mesh Components, Skeletal Mesh Components, etc.).
A bounding box is created around these primitives.
The system samples points across this bounding box (center, corners, etc.) to use for visibility testing.
Camera Frustum Test 🎥
The bounding points are projected into screen space.
If the required number of points are inside the active camera frustum, the actor is considered visible.
By default:
Optional Line Trace Validation 🎯
If Line Trace Bounding Points is enabled, each point is validated with a line trace:
The trace runs from the camera position to the bounding point.
If the line is blocked, the point is considered occluded.
If an actor passes all checks, it is marked as scanned for that screenshot.
Once an actor has been successfully scanned:
Its metadata is attached to the screenshot payload.
This data is passed to the On Screenshot Taken delegate on the Player Character’s BPC_ODK_ScreenshotComponent.
Example flow:
Player presses IA_SelfieMode_TakeSnapshot.
Screenshot is saved locally and minted.
Delegate fires → returns metadata including any scannable actors in view.
The BPC_Scannable component exposes several options for tailoring how actors are scanned and represented:
Scan Range (float) → Maximum detection distance from the camera (default: 1000).
Required Points (int) → Number of bounding box points that must be inside the frustum (default: 1).
Line Trace Bounding Points (bool) → Enables occlusion checks for more accurate results.
Friendly Name (FString, Exposed Variable)
If set, this is included in the screenshot’s metadata JSON.
If not set, a fallback name is auto-generated from the actor’s name.
GetScanInfo Override 🔧
Developers can subclass BPC_Scannable and override the GetScanInfo function.
This allows returning a custom JSON object with arbitrary fields.
Example: rarity, quest state, or any game-specific attributes.
Origin Modifier (FVector) → Shifts the generated bounds center.
Extent Modifier (FVector) → Expands or shrinks the generated bounds extents.
Useful for fine-tuning where an actor’s scannable area is relative to its meshes.
Setup
Attach BPC_Scannable to any actor you want to be detectable in Selfie Cam mode.
Configure
Adjust Scan Range and Required Points to match how “strict” the scanning should be.
Enable Line Trace Bounding Points if you need occlusion accuracy (e.g. actors behind walls shouldn’t be detected).
Take Screenshot
Player enters Selfie Mode (IA_ScreenshotCamera_Toggle).
Camera checks for nearby scannable actors.
If visible, bounding box points pass frustum/trace tests → actor metadata is collected.
Handle Metadata
When the screenshot is taken, the On Screenshot Taken delegate on BPC_ODK_ScreenshotComponent fires.
This delegate returns:
👉 With this system in place, screenshots taken by players don’t just capture visuals — they also capture contextual metadata about the world, making them perfect for collectibles, quests, or social sharing features.

It is a good idea to test how your avatar is going to work in the Otherside by setting up a small test scene to preview some animations.
In order to do this we are going to:
Create a new ODK project
Create a new blueprint that will
Load a GLB into the ODK at runtime
Token Handlers are objects that react to token changes inside the ODK wallet. They live on the wallet component of the Morpheus player character and define what happens when a token is added, removed, or updated — such as triggering a quest, showing some UI, or playing a VFX.
The system is modular, data-driven, and highly extendable via Blueprints.
Token Handlers are configured on the player's wallet component:
📍 BPMC_ODK_WalletComponent
Found on: BPM_ODK_PlayerCharacterBase
Trigger Execution CompleteBPFL_ODK_WalletHelpers → GrantTokenToWalletBP_TaskFlowAction_GrantAchievementFromAttachedToken1000 unitsRequired Points = 1 (i.e. only one point needs to be inside the camera view).
You can:
Ignore specific actors using a tag.
Ignore specific component classes under Advanced > Component Classes to Ignore (defaults include Niagara systems, Groom components, widgets, nameplates, and others).
Any attached metadata from scannable actors.

Inside the component, you’ll find a map:
Each entry defines a filter set with an arbitrary name and the handler that should respond if a token passes that filter.
Filters for tokens with type == "quest"
Handler: BP_ODKTokenHandler_Quest - Shows Quest UI information
Each filter in the filter set must implement the function:
This function inspects the token metadata and determines if it matches your logic.
If ALL of the filters return true, the assigned Token Handler is activated.
You can find reusable example filters in the plugin:
Checks the type field in token metadata
Accepts a whitelist of strings
Example: match type == "quest" or type == "achievement"
Used to group filters together in more advanced logic
Contains:
Filters[]: array of AND'd filters - All filters must pass
OR Filter: a single filter that acts as an OR condition.
The OR filter is a single filter because it allows nesting of ConditionGroups, allowing complex condition trees
Example:
To create custom filters:
Inherit from BP_ODKTokenFilterBase
Implement IsRelevantToken
Add any parameters you want to expose (e.g. tags, substrings, reward keys)
Custom filters give you full control over how tokens are routed, enabling:
Campaign-specific logic
Filtering by metadata fields like XP, group, or even token balance
Handlers inherit from BP_ODKTokenHandlerBase and define what happens when a relevant token event occurs.
Each handler receives the TokenUpdate function containing the filtered token and the update type (Add, Update, Remove etc)
Token handlers allow for custom logic for a filtered token, for example:
For all tokens with "type" "quest"
Fire off UI or sound
Trigger Task Flows
For all tokens with "type" "badge"
Start cinematics
Queue unlocks
Animate overlays
Use meaningful filter set names ("quest notifications", "achievement unlocked")
Reuse modular filters (e.g. shared TokenType filters)
Keep filters lightweight — heavy logic should live in handlers
Avoid putting too much logic inside IsRelevantToken — keep it as a pass/fail gate
Map<string, BP_ODKFilterSet>IsRelevantToken(TokenData) → bool( TokenType == "quest" )
OR
( ConditionGroup
(TokenType == "achievement")
AND
(ChainID == "33139")
)1. Token is added/removed/updated
2. Each Filter Set checks if the token matches its filter(s)
3. If matched → corresponding Token Handler is triggeredApply animations
Let's start by creating a new project. Start the ODK Launcher and make sure you are on the Templates Tab.
Once in the project use the Content Browser to navigate to the map called Empty_P, double click on the icon to open the map.
Create a new folder called Blueprints at the root of the content folder.
Right click in the empty window to and click Blueprint Class.
Select Actor
Name the blueprint BP_AvatarPreview and then double click on the blueprint icon to open up the editor. Once in the editor change the visible tab to EventGraph
Press the + next to Variables to add a new variable called Animations
Change the type to Animation Asset
Make the variable public by toggling the eye next to it.
With the variable still selected in the details panel on the right update the variable type to be Array.
We are going to add two more variables:
GLB Path
Type - File Path - Single
Public
Spacing
Type - Float - Single
Public The Variables section should now look like:
In the EventGraph press tab to add a node glTF Load Asset from Filename
Drag and drop GLB path from the Variables panel into the Event Graph and select Get GLB Path
Right Click on the GLB Path pin and click Split Struct Pin
Now you can connect the pin on GLB Path to the file name on the Load Asset from Filename node.
The next node we will be adding is the Load Skeletal Mesh Recursive node. You will have to uncheck the Context Senstive checkbox in the top right hand corner of the node search window to find it.
Connect the pins as shown in the images below to the Load Asset node.
Pull a connection out from the Loader Config Pin on the glTF Load Asset from Filename. Reenable the Context Sensitve check box and scroll to the bottom and select Make GlTFRuntime Config.
In the dropdown for Transform Base Type choose YForward
Press the down arrow at the bottom of the Load Skeletal Mesh Recursive node and drag out a connection from the Skeletal Mesh Config Pin. Scroll to the bottom and select Make GlTF Runtime Skeletal Mesh Config
From the Skeleton dropdown select SKEL_UE5Mannequin
From the Variables panel drag Animations out to the Event Graph and select Get Animations.
From the Pin on Animations pull off a connection and then select For Each Loop.
Connect the Exec pin from load skeletal mesh to the For Loop.
Add an Add Skeletal Mesh Component Node
Pull a connection off of the Relative Transform Pin and select Make Transform
Split the Location Struct Pin
Next we are going to multiply the Spacing variable by the Array Index from our Loop node. We can also connect the Loop Body Pin o the Loop node to the Exec Pin of the Add Skeletal Mesh Component.
If you haven't Compiled the blueprint yet we can do that now and then set a default value of 200.0 for our Spacing
On our Skeletal Mesh Component, pull out a connection from the Return Value of the Add Skeletal Mesh Component node and select `Set Animation Mode.
In the dropdown for In Animation Mode set it to Use Animation Asset
Also pulling from the Return Value of the Add Skeletal Mesh Component node add an Override Animation Data node, connecting the In Anim to Play to the Array Element of the for loop.
Again pulling a connection from the Add Skeletal Mesh Component node add a Set Skeletal Mesh Asset with the New Mesh Pin connected to the Return Value of Load Skeletal Mesh Recursive.
Back in our main level editor window, drag and drop the BP_AvatarPreview into the level.
In the details panel in the bottom right of the screen we can an animation by pressing the + Next to the Animations label.
For our first test animation I am just going to add a Range of Motion or ROM animation sequence.
Next we can click on the three dots next to the GLB Path field to select the GLB we downloaded from the avatar web tool.
Once that has been entered press the green arrow at the top of the level editor to preview.
We can add more animations to the the Animations list to be able to preview many animations at once. Using emotes is a good way to test extreme poses.
We have successfully brought a character the whole way from nothing to a properly scaled working avatar for the Otherside. Now as we make small tweaks we can quickly see them represented in our test scene by replacing the file that the test scene is referencing.
For this collection we have 3 Different Trait types. We can combine our separate meshes into a unified mesh for each trait type, and apply the appropriate material. We end up having 1 mesh object for each trait in our outliner.
In order to export unique avatars we will want to only enable the meshes that make up a specific token. We can create a python script in Blender that reads in the JSON we created earlier and enable and disable visibility to export specific token glbs.
The easiest way to do this would be to enforce a naming convention when creating meshes in the outliner something like:
{trait_type}_{trait_name}
i.e.
head_smiley_face
So the general steps would be:
Read in collection metadata
Loop through each token
hide all meshes
enable meshes from token metadata
export GLB from Blender.
Download the standalone Binary for node.js v20.11.1
Windows
Mac
Extract the .zip and place it in a folder relative to your blender file
open up a terminal and change the directory to the folder you just extracted i.e. cd /path/to/avatar-tools-main
add node_bin to PATH
Windows set PATH=..\node_bin;%PATH%
Below is a very basic script that can be run within Blender to export all avatars for a collection. There is plenty of room for improvement to fit specific needs and workflows but this should give a foundational look at how to create a simple process for generating all unique avatars.
We are going to roughly block out the dimensions of our character and manually step through the entire process to get an avatar rendered in the ODK.
We want our Boxie character to be around the height of a Voyager so roughly ~1.8 meters tall. Too tall or wide and the character may not fit through certain areas or other assumptions might break too small traversal will look very strange and again some assumptions may break. That is why making characters between 1 m and 2 m is a hard requirement.
If you aren't already there switch to the Layout view in Blender and delete the Camera, Light, and Default Cube. This is also a good opportunity to save your file.
Let's download the Avatar Height Guide from above, and then in Blender create a Plane. We are going to apply the Height Guide to the plane to get a rough idea of how our avatar will compare to ones already in the Otherside.
If the sidebar on the right isn't already visible click on the small right arrow < to open it up.
We are going to edit the scale settings so the image is approximately to scale. It isn't pixel perfect but it will get you in the ballpark. Set the Scale X value to 3.825 and the Scale Y value to 2.150.
Now we can add the height guide to the plane so we can reference it when doing our block out. Switch over to the Shading tab.
With the plane selected press the New Button on top of the Shader Editor panel.
You can go ahead and just delete the node labelled Principled BSDF and then drag and drop the Avatar Height Guide file into the Shader Editor.
Connect the Color pin on the right side of the Image Node to the Surface pin on the left side of the Material Output node.
If you notice the color looks a little muddy. This is because by default Blender uses a View Transform called AgX. This view transform is useful for photoreal results in renders from Blender. Since we aren't going to be rendering our characters in blender we want to switch the View Transform to Standard.
In the properties panel on the right click on the Render Properties tab which looks like the back of a DSLR camera.
At the bottom there is a section called Color Management. Set this to Standard
Nice! Let's jump back over to the layout tab. At first you will not be able to see the image. We can easily change this by clicking on the dropdown in the top right hand side next to the four little sphere icons.
For now we are going to change our Lighting to Flat and change the Object Color to Texture
Now we can align the feet of the avatars in the guide to be aligned to 0 on the X axis. For this we will need to rotate the plane 90 degrees about the X-Axis and scootch it up 0.785 m along the Z-axis. I am also going to move it slightly to the right so the voyager is aligned with the center of the scene, 0.18 m along the X-axis.
For my block out I am going to use 10 different primitive cubes scaled to represent the head, torso, arms and legs.
The simplest way to transform 3D objects is to click on the transform type on the left hand side. To work faster using keyboard shortcuts refer to the Blender documentation ->
You can add cubes just like we added the Plane above.
I added my cubes and lined it up pretty closely to the Voyager. I am not feeling very creative so I am not going to deviate from that general shape, but you do have some flexibility to change the proportions slightly to better match your character.
I am going to hide the avatar height guide by pressing the eyeball next to the plane in the outliner and switch the viewport back from flat lighting to studio lighting to get a better look at the shape of Boxie.
Take this opportunity to name the boxes something logical. The standard when creating characters is if you are using Left/Right is to always use the characters Left/Right and not the viewers. I also know what is coming next so I named the cubes for what bones will drive them once we get to weighting.
If you feel confident you are done with the reference image, you can delete that now.
We call this weighting because it involves the weighted influence of a bone to every single vertex. For example with Curtis the stomach area might have weights from multiple spine bones so that it deforms smoothly as the spine twists.
There is a hard limitation in the Otherside that there can only be 4 bone influences per vertex. You don't have to manage this yourself, Blender will handle that on export of the GLB.
Obviously manually assigning weights to individually vertices is usually not feasible as there could be thousands of vertices and almost 90 bones.
In most cases you will use some sort of tool to help with weighting, usually starting with an automatic weight tool, and then if there are still issues using a process called weight painting to fix specific problem areas.
Blender has some automatic weighting tools and there are several free and paid addons that can may work better such as:
https://superhivemarket.com/products/voxel-heat-diffuse-skinning
or free one from the same author
https://github.com/meshonline/Surface-Heat-Diffuse-Skinning
This guide is for the simplest avatar collection so instead we are going to rigidly bind our skeleton. This means each box of Boxie will only be influenced by one bone. We will completely side step automatic weighting and weight painting and instead manually assign full weights to one bone for each box.
Download the ODK_Base_Skeleton from and import it into Blender.
When moving between different software packages sometimes one may use one meters and another uses centimeters or one has the up axis as Z and the other has it as Y. In this case we can easily work around this by applying transformations in Blender to fix those issues.
With the newly imported Armature selected we can Apply -> All Transforms.
I am also going to to rename the armature for organization.
The ODK Base Skeleton doesn't exactly line up with our character. We will need to adjust the bones so that it fits better.
To enable moving bones select the armature and change the mode from Object Mode to Edit Mode
You can select bones in the viewport or in the outliner.
To move a bone and all it's children select the bone, and with the mouse in the viewport press Shift - G and then from the list select Children
Be careful when editing the Armature if you have symmetry mode enabled. It can change the orientation of joints causing the arms to have offset rotations.
Holding down the Alt key while rotating the scene with the middle mouse button will snap the view into Orthographic mode which makes it easier to line up the bones.
Now that the armature is lined up we can move onto binding the meshes to the armature. This process:
Makes the meshes children of the armature object
Creates vertex weights for each object
Since we are manually setting the weights we are going to skip the automatic weighting and create empty vertex weights.
Make sure we are back in Object Mode and select the armature object. While holding down shift select the rest of the objects.
With the cursor over the 3D Viewport press Ctrl + P to open the parenting menu and select With Empty Groups
The meshes should now be children of the armature object and have vertex weights assigned to them. Currently all vertex weights should be assigned to zero.
To start editing vertex weights click on the vertex weight tag next to the object, you can filter the list in the Properties Panel by unfurling the filter bar below all the weights and typing. I intentionally named all the meshes the same as the bones I was going to bind them to.
With the appropriate vertex weight group selected make sure you are in Weight Paint mode and either select Set Weight from the dropdown menu at top of the viewport, or press Ctrl + X on the keyboard.
Repeat these steps for all the other meshes that need to be rigidly bound. To switch between meshes that are activated for weight painting you can click on the dot next the mesh in the Outliner.
We can now export a GLB. I am going to only items that are visible, that way if you still have the reference image in the scene it will be skipped.
Make sure the format is glTF Binaryt (.glb) and under Include Visible Objects is checked.
To do a quick test of the avatar open up in a browser and drag the avatar into the window.
There shouldn't be any errors in the bottom left hand window and if you press Use Sample Animation the avatar should animate cleanly.
This tool will also do some minor adjustments and reposing to ensure that the avatar will work in the Otherside. To save the fixed avatar from the tool press the export button in the top right hand corner.
Date of change: 15/08/2025
Affected Features: UX Skins
What’s broken and why?
M2 has deprecated their skinning system which the ODK still has dependencies on. This dependency will be removed in a subsequent ODK release.
How to fix it?
When updating your existing ODK project, please add the following entry to DefaultM2Deprecated.ini
A Badge Token is a simple on-chain collectible or reward that represents an achievement, status, or cosmetic marker for a player. Unlike quests or tasks, it doesn’t track progress or drive gameplay — it's often used for visual trophies, event attendance, or milestone recognition.
At the time of writing, the Creator Portal doesn’t have a dedicated “Badge” template — so you’ll start with an Image template and update the metadata manually.









































































Change the folder name from node-v20.* to node_bin
Download the .zip from https://github.com/mml-io/avatar-tools
Mac/Linux export PATH=../node_bin;$PATH
run npm install
run npm run build --workspace packages --workspace clis --workspace tools

Click “Define New Object”
Select your experience from the dropdown
Choose the “Image” template (this is used as a base to define a badge)
You’ll now see a basic definition form.
🏷️ Required Fields:
name
Display name of the badge
description
Short description of what the badge is
image
The display icon for the badge
After filling out the form you will be taken to the metadata output.
We need to define this token as a badge. The image template does not contain a type field:
Add the line "type": "badge", (remembering to include the ,) to the metadata
🧾 Final Badge Token Metadata:
📌 Why this matters:
The type field tells the in-game wallet and TokenHandlers how to process this token. Without it, the badge will not trigger the correct logic or appear in badge-specific views.
Before minting:
Price → Set to 0 for development and testing
Transferable:
✅ True for dev/debug (allows deletion/resets)
🚫 False for production (prevents trading badge ownership)
After saving the metadata:
Click “Mint”
You’ll return to the Creator dashboard and see the generated Token ID, in this format:
Example:
Click the badge’s image on the dashboard to review metadata
You can update metadata at any time using the "Update Metadata" button
The badge token is now ready to be granted via Blueprint or automatically in-game using a Task Flow or Token Handler setup.
import os
import sys
import subprocess
import json
from pathlib import Path
import bpy
# set these folders/files relative to this blender file
WORKING_DIRECTORY = Path(bpy.data.filepath).parent
COLLECTION_METADATA = Path(WORKING_DIRECTORY, "boxie_metadata.json").as_posix()
OUTPUT_DIRECTORY = Path(WORKING_DIRECTORY, "glb_export").as_posix()
def get_node_binary_dir():
return Path(WORKING_DIRECTORY, "node_bin").as_posix()
def get_avatar_tool_dir():
return Path(WORKING_DIRECTORY, "avatar-tools-main").as_posix()
def tokenize_trait_names(token_data):
trait_names = []
for trait in token_data.get("attributes", []):
trait_type = trait["trait_type"]
trait_name = trait["value"].replace(" ", "_")
tokenized_name = f"{trait_type}_{trait_name}"
trait_names.append(tokenized_name)
return trait_names
def solo_meshes_by_names(mesh_names):
for obj in bpy.data.objects:
if obj.type != "MESH":
continue
if obj.name in mesh_names:
obj.hide_viewport = False
else:
obj.hide_viewport = True
def export_glb(output_path):
output_name = Path(output_path).stem
output_dir = Path(output_path).parent
temp_output_path = Path(output_dir, output_name+"_tmp").with_suffix(".glb").as_posix()
bpy.ops.export_scene.gltf(
filepath=temp_output_path,
export_format="GLB",
export_skins=True,
export_current_frame=True,
export_animations=False,
export_normals=True,
export_tangents=True,
export_image_format="AUTO",
export_jpeg_quality=100,
use_visible=True,
# export_draco_mesh_compression_enable=True,
)
if Path(output_path).exists():
Path(output_path).unlink()
conform_glb(temp_output_path, output_path)
Path(temp_output_path).unlink()
def conform_glb(input_path, output_path, skip_transparent_check=True):
node_bin = get_node_binary_dir()
env = os.environ.copy()
env["PATH"] = f"{node_bin}{os.pathsep}{env['PATH']}"
if sys.platform == "win32":
args = ["npm.cmd"]
else:
args = ["npm"]
args.append("run")
args.append("convert")
args.append("--")
args.append("-i")
args.append(input_path)
args.append("-o")
args.append(output_path)
if skip_transparent_check:
args.append("--skip-remove-transparency-from-materials")
results = subprocess.run(args, cwd=get_avatar_tool_dir(), env=env)
print(results)
if results.returncode:
output_encoded = results.stderr or results.stdout
raise Exception("Error Conforming Mesh")
def export_from_token_data(token_data, output_path):
mesh_names = tokenize_trait_names(token_data)
solo_meshes_by_names(mesh_names)
export_glb(output_path)
def show_all_meshes():
for obj in bpy.data.objects:
if obj.type != "MESH":
continue
obj.hide_viewport = False
def export_collection(data_path, output_directory):
# load collection data from JSON
if not Path(data_path).exists() or not Path(data_path).is_file():
raise Exception("Invalid Collection Data")
with open(data_path, "r") as f:
collection_data = json.load(f)
# create directory if it does not exists
output_dir_path = Path(output_directory)
output_dir_path.mkdir(exist_ok=True, parents=True)
for token_data in collection_data:
# build glb export path
output_path = Path(output_dir_path, str(token_data["id"]) + ".glb")
output_path_str = output_path.as_posix()
print("Exporting", token_data.get("name", token_data.get("id")), "to", output_path_str)
export_from_token_data(token_data, output_path_str)
show_all_meshes()
if __name__ == "__main__":
export_collection(COLLECTION_METADATA, OUTPUT_DIRECTORY)
{
"name": "Badge name",
"description": "This is a badge",
"image": "https://image.url"
}jsonCopyEdit{
"name": "Badge name",
"description": "This is a badge",
"type": "badge",
"image": "https://image.url"
}<ChainID>:<ContractAddress>:<TokenID>33139:0x1122334455667788991122334455667788991100:5Date of change: 15/08/2025
Affected Features: Deployment access
What’s broken and why?
Nothing is broken, however we now do a domain check when accessing your world within the web app to ensure that the world you are accessing is pointing to the same domain that you're accessing it from. This is essential to ensure correct functionality of the ODK. If you do access a world that is running on a different domain, you'll see this message. Simply select the "Redirect" button to be taken to the correct domain, and then enter your world.
Date of change: 15/08/2025
Affected Features: Emotes
What’s broken and why?
The coop emote and standard emote flows have been unified to some degree. There may be more unification work in future. The BPC_ODK_EmotesComponent has become the morpheus actor component BPMC_ODK_EmotesComponent and now lives on the BPM_ODK_PlayerCharacterBase. Some functions have had an API update. Following this documentation should help with those change: https://docs.otherside.xyz/odk-documentation/documentation/odk-plugin/emotes
How to fix it?
You will now need to grab the component off the BPM_ODK_PlayerCharacterBase.
Date of change: 13/08/2025
Affected Features: Avatar management
What’s broken and why?
To unify where allowed avatar collections are retrieved, we've moved the definition into the backend. This means that if you need to modify your avatar collections going forward (outside of Yuga's defaults) then you'll need to request an update to your project.
How to fix it?
Delete any overrides you have for your project.json to ODK.AvatarSelector.AllowListCollectionContractsArray , and if you need specific collections filtered for your project, please reach out within your nearest Yuga support channel.
Date of change: 12/08/2025
Affected Features: Token management
What’s broken and why?
To support token management across multiple chains (not just ape chain), we've introduced the concept of an "experience group" that defines what chain + contracts your world should be operating on. This information is provided on startup, and you're able to retrieve it via BP_ODK_WalletWorldService as you normally would (previously the information was hardcoded within live config). The one difference is that TokenIds themselves will need to be updated dependent on the chain you're operating on. That means that utilities like BP_GetTokenIDBase and BP_GetTokenIDFromLiveConfig have been updated to be able to provide two token ids (the correct ID will be automatically grabbed dependent on the chain you're operating on).
How to fix it?
For each instance of BP_GetTokenIDBase , be aware that you'll need to provide a curtis token id if/when you start operating on curtis. For each instance of BP_GetTokenIDLiveConfig , this object now expects an array of two numbers (instead of just a single number). Go through any definitions in live config, and update the schema to provide an array of two numbers. For instance:
Date of change: 12/08/2025
Affected Features: Live Config
What’s broken and why?
Previously the project.schema.json was versioned with project content, and both ODK and project values were added to this single file. This meant whenever you took an ODK update, projects needed to manually merge any new ODK config changes into their project.schema.json. We've now split the schema file and it'll be versioned with the ODK plugin itself. This means the project.schema.json file is available for exclusive use of the project, and you'll not need to do any manual merging going forward.
How to fix it?
Navigate to your project's project schema file (<ProjectDir>/Config/LiveConfig/Schemas/project.schema.json) and remove the "ODK" object blob from the file. If you've not added any of your own live config overrides to the project schema, you can remove the file entirely.
How to test it?
Start a PIE session in editor, and confirm that you see the green "schema generated successfully" toast appear.
Date of change: 12/08/2025
Affected Features: Startup flow
What’s broken and why?
To help simplify the startup logic, we've added a new bootflow singleton that coordinates retriving dynamic world information, and providing it to server/clients as part of the bootflow process.
The singleton is called BPM_ODK_BootflowSingleton and currently provides two purposes, but will be extended in future to solidify the ODK bootup experience.
Retrieve smart chain contract details and then replicate them out to users. This allows developers to easily switch between different chains + contracts. You can listen for this event in your internal logic by using the WaitForCondition node and waiting on ExperienceDataSet
Retrieve allowed avatar contract list and apply before user avatar is applied. This ensures your clients adhere to the current avatar contract policy deploy by Yuga. You can listen for this event in your internal logic by using the WaitForCondition node and waiting on AllowedAvatarsRetrieved
How to fix it?
For any previously created levels, please add the BPM_ODK_BootflowSingleton to the AdditionalSingletons array within the World Settings. For any new levels the singleton will be added automatically.
How to test it?
Ensure your bootflow starts correctly, and you're able to apply your set avatar model.
Date of change: 29/07/2025
Affected Features: Movement Modes
What’s broken and why?
As part of an effort to make movement modes more extendable and to simplify their structure, some breaking changes have been made. A new movement mode component (BPC_ODK_MovementComponent) has been added to the render target to help manage movement modes The main breaking changes are:
- BPE_Y_MovementModes has been removed. Checking for a given movement mode can be done by either querying the new BPMC_ODK_AnimVarsComponent MovementMode if needed on non auth clients or BPC_ODK_MovementComponentBase::GetCurrentMovementModeId on auth clients.
- BPMC_ODK_AnimVarsComponent has an updated event dispatcher: OnAnimationMovementModeUpdated. Ensure any code triggered when the anim vars component updates it's movement mode use this new event dispatcher are fixed up.
- If you need to change to a movement mode, you can use BPC_ODK_MovementComponentBase::TrySetMovementMode.
- You may need to change update some of your bindings to listening to the new event dispatcher: BPC_ODK_MovementComponentBase::OnMovementModeUpdated.
- If you have custom movement modes, it may be worth converting them to use the new ODK movement mode system. Documentation can be found here: https://docs.otherside.xyz/odk-documentation/documentation/odk-plugin/movement-modes
How to test it?
Ensure your logic behaves as it did before these changes.
Date of change: 17/07/2025
Affected Features: Mixpanel Analytics
What’s broken and why?
To allow support for more data types, in this case Int, Floats and Bools, we have added an enum to the event struct that allows you to tell the API which data type you wish to use.
How to fix it?
If you have existing mixpanel event structs being created (S_ODK_AnalyticsEventData), you'll need to open, recompile and save any BP that is creating them. They default to String type, so nothing else will change. However, you may want to take this opportunity to swap any Ints, Floats or Bools over. Just select the type you want from the Make Struct node, then hit the show more arrow at the bottom of the node to reveal the types. Here is an example of each type:
How to test it?
On you Mixpanel dashboard, you can view your events. If you select "Full JSON" view, you'll see that any event that has been swapped over will now be its correct type.
Date of change: 01/07/2025
Affected Features: UI Mode
What’s broken and why?
The API has been modified to more generic allowing generic uobject context to be used to request UI mode rather than widgets
How to fix it?
Simply use MarkContextNeedsUIMode and UnmarkContextNeedsUIMode instead of the old library helper fucntions.
How to test it?
Ensure UI mode works as you intended.
[/Script/JunoCoreUI.J_UISkinsSettings]
bEnableLegacySkinningSystem=True"TokenIDLiveConfigName": {
"type": "array",
"items": {
"type": "number"
},
"default": [1, 8],
"description": "On apechain, the token is 1. On curtis, it is 8."
}



Choose the “Quest” template
You’ll now see the Quest definition form
🏷️ General Fields
name
Display name of the quest
description
Summary of what the quest is about
image
URL to the image shown in UI
type
Must be "quest" — this enables quest tracking in-game
📦 quest Object Fields
group
Logical group for filtering in UI or in-game
xp
Optional XP awarded on completion
tasks[]
List of task definitions, each with a title and description
rewards[]
Optional: Array of Token IDs to grant on quest completion. For help creating a badge, see
After completing the form, you will be presented with the generated metadata
🧾 Example Quest Token Metadata
📌 Important: completionQuantity will be automatically set to one more than the number of tasks
This accounts for:
1 token to start the quest
1 token per completed task
Example: 3 tasks → completionQuantity: 4
→ 1 token to start + 3 tokens for each completed task.
Before minting:
Once development is complete this token should be non-transferable so that users cannot trade progress or restart the quest, but during the development process it is better to set this to transferable so that it can be deleted from a users wallet and allow the user to retry the quest.
Price → usually 0 for dev
Transferable:
✅ True during dev — allows testing/resetting progress
🚫 False for production — prevents replaying quests via trading tokens
After saving, click “Mint”.
You’ll be returned to the creator dashboard and shown a Token ID in this format:
Example:
33139
Chain ID (APECHAIN)
0x1122...
Contract address for your experience
:1
Unique identifier for this token
You can click the token’s image on the dashboard to view metadata.
This page reflects the JSON you just configured and is used in-game by clients and UI.
You can update the token metadata at any time using the "Update Metadata" button
Below are some general guidelines that have helped the Yuga create Avatar collections. This aren’t hard requirements but should help in the production of 10s of thousands unique avatars.
Concept Art
Clarity of Design: Does the concept art clearly convey the character’s design, including front, side, and back views?
Consistency with Brief: Does the character design align with the initial brief and the project’s artistic style?
Detail Level: Are the details sufficient to guide the modeling process?
Modeling
Proportions: Are the character’s proportions accurate and consistent with the concept art?
Topology Quality: Is the topology clean, with appropriate edge flow for animation and deformation?
Detail Level: Does the model capture all necessary details while maintaining a reasonable polygon count?
Maya Mesh Clean up: To use the Mesh Cleanup
Select the Mesh:
First, select the mesh object you want to clean up. You can do this by clicking on the object in the viewport or selecting it from the Outliner.
Open the Mesh Cleanup Tool:
Go to the top menu and navigate to
This tool is handy for preparing a mesh for further processing, especially when dealing with 3D models that need to be optimized for gaming or other real-time applications.
If you encounter specific issues during cleanup, you can adjust the options or manually fix the geometry where necessary.'
Here are key areas to consider as 3D Modeler:
1. Topology and Geometry
Clean Topology: Ensure the mesh is clean, with evenly spaced polygons and no unnecessary vertices or edges. This helps with smoother animation and better performance.
Avoiding N-gons: Stick to quads or tris, as N-gons can cause issues during subdivision or when deforming the model.
Edge Flow: Proper edge flow is crucial for smooth deformations in animations, especially in character modeling.
2. UV Mapping
Efficient UV Layout: Avoid stretching or overlapping UVs to ensure textures map correctly and appear consistent.
UV Packing: Maximize texture space by efficiently packing UVs, but avoid distorting the model in the process.
Seams Placement: Place UV seams in less visible areas to minimize noticeable texture breaks.
3. Scale and Proportion
Consistent Scale: Ensure all models are scaled consistently to avoid issues during the integration into scenes or with other models.
Correct Proportions: Keep the model's proportions accurate, especially when working from concept art or real-world references.
4. Detail Level
Appropriate Detail for Context: Balance the level of detail according to the model's use case (e.g., close-up vs. background assets).
Normal Maps for Detail: Use normal maps to add fine details without increasing polygon count.
5. Texture Quality
High-Resolution Textures: Ensure textures are of high resolution and free from artifacts, especially for hero assets.
Consistent Texturing: Maintain consistent texturing across the model, avoiding visible seams or inconsistencies.
6. Performance Optimization
Polygon Count: Keep the polygon count as low as possible without sacrificing quality, especially for real-time applications.
LOD (Level of Detail) Models: Create multiple levels of detail for the model to ensure it performs well at different distances.(Consider if LOD option is required)
7. File Organization
Naming Conventions: Use clear and consistent naming conventions for all assets, materials, and textures.
Layer Management: Organize your layers and groups logically to make the file easy to navigate.
Sculpting
Detail Accuracy: Are the high-resolution details well-defined and true to the concept art?
Subdivision Levels: Are the subdivision levels managed correctly to provide smooth transitions between levels of detail?
Baked Maps: Are normal maps and other baked maps free from artifacts and accurately represent the sculpted details?
SubD modeling
Subdivision surface (SubD) modeling is a technique that allows you to create smooth, high-resolution surfaces from a low-resolution base mesh. To achieve optimal results in SubD modeling, there are several important rules to follow:
1. Use Quads Exclusively
Stick to four-sided polygons (quads) as much as possible. Quads subdivide cleanly, creating smooth surfaces without causing artifacts. Avoid using triangles and n-gons, as they can create pinching and distortion when subdivided.
2. Maintain Even Polygon Distribution
Keep the topology evenly distributed across the model. This ensures that when the mesh is subdivided, the surface remains smooth and consistent. Avoid areas with overly dense or sparse polygons, as this can result in uneven smoothing.
3. Ensure Good Edge Flow
Design your edge loops to follow the natural contours and anatomy of the model. This helps maintain smooth deformations, especially in areas that bend or flex, such as joints. Proper edge flow also aids in adding detail to specific areas without affecting the entire model.
4. Use Edge Loops for Definition
Add edge loops strategically to define sharp edges and detailed areas. Placing an additional edge loop close to an existing one can help control the sharpness of the resulting subdivided edge. This technique is useful for creating crisp corners or specific details in the model.
5. Avoid Overlapping Vertices
Ensure that no vertices are overlapping or too close together. Overlapping vertices can cause issues during subdivision, leading to artifacts or unintended surface distortions.
6. Minimize Pole Usage
A pole is a vertex where more than four edges meet. While sometimes unavoidable, poles should be minimized and placed in less noticeable areas of the model. Poorly placed poles can cause pinching and other artifacts when the mesh is subdivided.
7. Use Crease and Hard Edges Sparingly
Some 3D software allows you to mark edges as creased or hard, preventing them from being smoothed during subdivision. Use this feature sparingly, as overuse can result in unnatural transitions between smooth and sharp areas.
8. Keep Base Mesh Simple
Start with a simple base mesh before adding details. SubD modeling is most effective when the initial mesh is clean and simple, allowing for easy adjustments and predictable results after subdivision.
9. Check Topology with Subdivision Preview
Regularly preview your model with subdivision applied to ensure that the topology holds up and that there are no unexpected issues. This helps catch potential problems early in the modeling process.
10. Maintain Model Symmetry
When working on symmetrical models, ensure that the symmetry is maintained throughout the modeling process. This simplifies the modeling workflow and ensures that both sides of the model behave consistently when subdivided.
11. Plan for UV Mapping
Keep in mind how the topology will affect UV mapping. Clean, evenly distributed quads will make UV unwrapping more straightforward, reducing the potential for stretching or distortion.
12. Avoid Non-Manifold Geometry
Non-manifold geometry (edges shared by more than two faces or disconnected vertices) can cause problems during subdivision and rendering. Always check your model for non-manifold elements and correct them as needed.
By following these SubD modeling rules, you can create high-quality, smooth models that are well-suited for animation, rendering, and other advanced workflows.
SubD Examples:
Retopology
Optimization: Has the polygon count been optimized without losing important details?
Edge Flow: Is the edge flow appropriate for animation, particularly around joints and areas of high deformation?
Topology Efficiency: Is the topology efficient, minimizing unnecessary polygons while maintaining shape integrity?
In 3D retopology, there are several common mistakes to avoid to ensure the model is optimized for animation, performance, and texturing. Here are some key things not to do:
Overly Dense Topology: Avoid creating too many polygons, especially in areas that don’t require high detail. This can make the model unnecessarily heavy and hard to manage.
N-Gons and Triangles: N-gons (polygons with more than four sides) and triangles should generally be avoided, particularly in areas that will deform during animation. They can cause unpredictable shading and deformation issues. Aim for quads (four-sided polygons) as much as possible.
Uneven Distribution of Polygons: Don’t create a topology with uneven polygon distribution. This can result in poor deformation during animation and can make it difficult to achieve smooth surface details.
By avoiding these mistakes, you can create a clean, efficient, and functional topology that will serve well in the final stages of the production pipeline.
UV Unwrapping
Seam Placement: Are seams placed strategically to minimize visibility in prominent areas?
UV Layout: Is the UV layout efficient, maximizing texture space and minimizing stretching?
Packing Efficiency: Are UV islands packed effectively to ensure high-resolution textures?
Hard surface UV examples: straighten UVs where you can.
Texturing (Hand painted)
When creating 3D hand-painted textures, particularly for games or stylized art, there are several key rules and best practices to follow to ensure a consistent and high-quality result. Here’s a guide to help you with the process:
Understand the Style:
Art Direction: Know the style you're aiming for. Hand-painted textures often emphasize stylization over realism, with exaggerated colors and simplified details.
Reference Gathering: Collect references of the style you're targeting, whether it’s cartoony, fantasy, or another specific aesthetic.
UV Mapping:
Rigging
Bone Structure: Is the skeleton appropriately structured for the character’s anatomy and intended range of motion?
Skinning: Does the character deform correctly during animation, with smooth transitions and minimal distortions?
Control Rig: Are the controls intuitive and functional for the animators, allowing for a wide range of expressions and movements?
Animation
Cycle Quality: Are standard animation cycles (e.g., walk, run, idle) smooth, natural, and loop seamlessly?
Custom Animations: Do custom animations meet the character’s role requirements, showing personality and purpose?
Fluidity: Are the animations fluid and believable, with no noticeable errors or jerks?
Our approach to QA’ing large collections quickly and effectively.
Most collections minimum number of Avatars that represent all traits is between 50-300, we iterate on that subset of avatars fixing issues until all traits have been validated in an Assembled Avatar.
Then we add that list to a larger list (usually 25% of total collection size) to identify any issues that would have been missed in the 50-300 QA group.
We feel this is a good compromise between speed and coverage, however each collection poses unique challenges this QA strategy might not be applicable to all cases.
To test avatars as close to how they will appear being loaded into the game from an external source without going through any ODK tooling you can use the gltf runtime plugin to load GLBs into unreal. You can load an individual GLB into the editor by dragging and dropping into the content browser.
Do a visual inspection of textures to ensure they look correct.
Common Issues:
Lines appear at UV seams, items appear concave when they should appear convex or vice versa.
The normal map format for GLB is Y+, this is common for opengl shaders. This is different than directx or Y-
Lines appear at UV seams, depth of detail is not as deep as it is supposed to be.
Based on topology and weighting it is very common for overlapping polygons to intersect. For example if the topology and weighting of a t-shirt doesn’t exactly match the topology the body may clip through the shirt while the avatar is deforming. There are two main routes to remediate this issue.
Model the traits/slots so when the body has the t-shirt, there are no polygons on the body under the shirt.
Create overlap masks that hide polygons under the overlapping slot.
Visually identify any portions of the Avatar which is not deforming as expected with the set of animations that are included in the ODK .
Checking every single animation sequence may not be feasible, if not checking against the entire list, skipping variations of the same type of animation, i.e. idle and clapping is fine. Prioritize locomotion for general gameplay, however the range of motion will be pushed to the limit in emotes, i.e. any of the dancing.
Creating an animation blueprint that loops through animations, or allows the selection of specific animations may be useful if opening up animations to a QA team natively on ODK/M2, but loading a skeletal mesh into unreal with the UE5 skeleton set as the mesh skeleton will allow you to play animations on the mesh in the Editor quickly and easily.
Date of change: 02/07/2025
Affected Features: Users with existing ODK projects
What’s broken and why?
Currently ODK schema is embedded within the `ODK` section within the project.schema. For legacy reasons, the ODK schema was instantiated with each template, rather than stored with the plugin content itself. To ensure you're operating with the latest, correct ODK schema, please integrate the latest schema here into your project.
How to fix it?
jsonCopyEdit{
"name": "Example Quest",
"description": "Do your exercises",
"image": "https://generatedimage.link",
"type": "quest",
"quest": {
"type": "global",
"group": "whitespace",
"displayType": "quantity",
"completionQuantity": 4,
"xp": 123,
"tasks": [
{
"title": "Jump 3 Times",
"description": "Perform 3 jumps to get your body moving!"
},
{
"title": "Run 500m",
"description": "Run a total distance of 500 meters. Cardio is key!"
},
{
"title": "Glide for 100m",
"description": "Glide gracefully through the air for at least 100 meters!"
}
],
"rewards": [
"33139:0x1122334455667788991122334455667788991100:3",
"33139:0x1122334455667788991122334455667788991100:4"
]
}
}ChainID:ContractAddress:TokenID33139:0x1122334455667788991122334455667788991100:1Adjust Cleanup Options:
The Cleanup Options window will pop up. Here, you can choose specific issues to clean up. The options include:
Remove Geometry: For deleting specific types of geometry like non-manifold edges, lamina faces, zero area faces, etc.
Tessellation Cleanup: For correcting tessellation problems, like polygonal faces with more than four edges.
Repair Geometry: Fixes issues such as zero length edges and zero geometry area.
Set the Operation Mode:
Select Matching Polygons: This highlights the problematic areas in the mesh without making changes, allowing you to see where issues exist.
Cleanup Matching Polygons: Automatically fixes the problems based on the criteria you set.
Cleanup Non-Planar Faces: Specifically targets and fixes non-planar faces.
Execute the Cleanup:
Once you've set your options, click Apply or Cleanup to execute the operation.
Review the Results:
After the cleanup, review the mesh to ensure that the issues have been resolved. You can use the Select Matching Polygons option again to double-check.
Pinching and Stretching: Don’t create areas where edges are too close together (pinching) or too far apart (stretching). This can lead to issues with texturing and shading, as well as poor deformation in animations.
Overcomplicating the Mesh: Avoid adding unnecessary loops or geometry that doesn’t contribute to the form or function of the model. This can lead to increased file size and more complex rigging and animation processes.
Inconsistent Polygon Sizes: Keep polygon sizes as consistent as possible, especially in areas that require smooth deformation. Large variations in polygon size can cause issues with normal maps and other texturing processes.
Ignoring the Silhouette: Don’t neglect the silhouette of the model when retopologizing. Ensure that the topology supports the overall shape and form, maintaining the original design's integrity.
Neglecting UV Layout Considerations: Retopology should consider the eventual UV layout. Avoid creating complex topology that would make UV unwrapping difficult.
Overlapping Geometry: Ensure that there are no overlapping vertices, faces, or edges. Overlapping geometry can cause issues in rendering, texturing, and animation.
Efficient UV Layout: Ensure your UVs are well laid out with minimal stretching. This will make painting easier and more accurate.
Texture Space Optimization: Use as much of the UV space as possible to maximize texture resolution, avoiding empty areas in the UV layout.
Consistent Texel Density: Maintain consistent texel density across the model, so the details are uniform in size.
Base Colors and Gradients:
Block in Base Colors: Start by laying down base colors, defining the overall color scheme of the object. This serves as a foundation for further details.
Gradient Application: Apply subtle gradients to avoid flatness. Gradients can suggest light direction or simply add visual interest.
Painting Details:
Use of Brushes: Work with soft and hard brushes to create sharp edges where needed and soft transitions elsewhere. Experiment with different brush settings for unique textures.
Material Definition: Clearly define different materials (wood, metal, cloth) by painting in their characteristic details and surface qualities.
Hand-Painted Shading: Paint light and shadow directly onto the texture. This is a key part of the hand-painted style, as it doesn’t rely on real-time lighting.
Highlights and Speculars: Manually add highlights to areas where light would naturally catch, avoiding the need for dynamic lighting. Stylized highlights can enhance the visual appeal.
Color and Contrast:
Color Harmony: Stick to a defined color palette to maintain harmony across the texture. Avoid over-saturating or under-saturating colors unless the style specifically calls for it.
Contrast: Add contrast by differentiating between light and dark areas. This adds depth and helps the texture read better at a distance.
Detailing and Wear:
Texture Details: Add small details like scratches, wear and tear, or seams where appropriate. These details can make a texture feel more lived-in and believable.
Edge Highlighting: Use edge highlights to emphasize the edges of objects, making them stand out more.
Consistent Detailing: Ensure that the level of detail is consistent across the entire texture, preventing any one area from looking too busy or too plain.
Avoiding Procedural Looks:
Hand-Paint Everything: Avoid procedural or automatic tools as much as possible. The charm of hand-painted textures comes from their personal, crafted feel.
Break Repetition: Add small variations to prevent patterns from looking too uniform or repeated.
Final Touches:
Texture Review: Continuously review the texture on the model to see how it looks from various angles and distances.
Polishing: Once the main texture work is done, add any final touches like subtle noise, color adjustments, or small details that enhance the overall look.
Testing in Engine: If the texture is for a game, always test it in the engine to see how it looks under different lighting conditions and in the context of the game.
Item is shinier than it is supposed to be.
At some point in the pipeline the texture is being set to an sRGB Gamma Curve. Make sure the image transfer function is raw/linear.
Texture appears blocky
Try to avoid using JPG compression with Normal Maps, especially on almost flat surfaces.
If normal map is fine check compression settings on other texture maps.





























project.schema.json that lives at <project_workspace>\Config\LiveConfig\Schemas. If you haven't made any local project edits, you can safely just replace the file, otherwise you should merge the content with your local changes.Date of change: 30/06/2025
Affected Features: Otherside Dashboard and GFN streams.
What’s broken and why?
Due to us needing to authenticatee with Privy we need the Connect button on the dashboard to send the user via the web app rather then the legacy GFN streaming page. This means that users on older versions of the ODK will be unable to launch streams via the dashboard.
How to fix it?
Upgrade your project to ODK 8.2, this will then ensure that you are using Privy as authentication in your project and the Connect button will work again
Date of change: 27/06/2025
Affected Features: Bubbles
What’s broken and why?
The bubbles chat feature has been isolated into it's own template within the ODK ecosystem.
How to fix it?
If reliant on the bubbles feature, please integrate the Bubbles Template from the ODK Launcher, and migrate the content within to your project.
Date of change: 18/06/2025
Affected Features: Pause/Settings Menu
What’s broken and why?
The default Pause/Settings menu widget is now set on the PlayerController > BPC_ODK_PauseMenuControlComponent and the default widget has been changed from the M2 settings menu to WBP_ODKSettingswidget that leverages the ODK Base UI
How to fix it?
If you have previously used a custom settings widget, this should be set on the BPC_ODK_PauseMenuControlComponent component.
Date of change: 12/06/2025
Affected Features: Inspector Admin
What’s broken and why?
As part of the class hierarchy rework, the way we determine if you can access the Inspector has been updated.
How to fix it?
For any role that you want to have access to the Inspector (historically just Director) add the Capabilities.Morpheus.InspectorEnabled capability to the GrantedCapabilities field within your data table for those roles.
Date of change: 11/06/2025
Affected Features: Avatar Selector
What’s broken and why?
As we've moved to using the Web UI for more in-game functionality, we've retired the existing avatar selector (BPC_AvatarSelector and associated content).
How to fix it?
If you were previously relying on explicitly triggering the avatar selector during gameplay, instead you should use the Web UI for selecting a player character. To do so call OpenAvatarOverlay on the BP_WebBrowserWorldService and the web browser will present the user's avatars to them.
How to test it?
Validate that your users can correctly update their avatars when you trigger the avatar selector.
Date of change: 11/06/2025
Affected Features: Crowd Audio
What’s broken and why?
We have moved away from the soon to be depreciated crowd audio component and have our own ODK version.
How to fix it?
Simply switch out the BPMC_CrowdAudio component to BPMC_ODK_CrowdAudioComponent. They should have the same interface.
Date of change: 02/06/2025
Affected Features: Attachments
What’s broken and why?
Logic that handled adding attachments to the player has been moved to a new morpheus component: BPMC_ODK_AttachmentsComponent. This functionality was moved off BPM_ODK_PlayerCharacterBase. This means you may have some bad references in your projects
How to fix it?
Simply grab the component off your morpheus player character and use that instead of directly interfacing with BPM_ODK_PlayerCharacterBase.
Date of change: 02/06/2025
Affected Features: UI Mode
What’s broken and why?
The UI mode world service has been moved to a component on the player controller. Some functions that were previously on the player controller have been moved to this component. Additionally, now it supports changing UI mode from anywhere, not just widgets. If you interfaced with the UI mode world service directly, you should now go through the player controller component.
How to fix it?
Update you blueprint logic to use the new player controller component.
Date of change: 23/05/2025
Affected Features: Profiles
What’s broken and why?
With the move to a new authentication backend, we have a new mechanism for retrieving your user's profile infomation.
How to fix it?
For any prior levels, you need to manually update the "Profile Data Provider Class" to BP_ODK_WPProfileDataProvider
How to test it?
Log in and validate that your characters are retrieving their player names and other profile information correctly.
Date of change: 20/05/2025
Affected Features: Emotes
What’s broken and why?
Previously, the default emote array was defined in the roles Data Table, while CoOp emotes were specified in the BPMC_CoopEmote component and purchasable emotes in the BPC_ODK_EmotesComponent. These separate configurations have now been unified into a single location: the BPC_ODK_EmotesComponent on the Player Character.
This consolidation simplifies emote management by centralizing all emote definitions—default, purchasable, and CoOp—in one place and enables runtime overrides of the emote list, for both default and CoOp emotes.
The current roles data table definitions will still work, but will likely be deprecated in the future and this workflow will no longer be supported.
How to fix it?
• Remove the emote definition from the roles data table.
• Create a Data Asset of type PDA_ODKEmoteCollection with your defined emotes
• On your Player Character, find the BPC_ODK_EmotesComponent and assign the Data Asset to the ODKEmoteCollection variable
How to test it?
Play in editor and bring up your emote wheel. The defined emotes should appear on the wheel.
Date of change: 18/05/2025
Affected Features: Avatars
What’s broken and why?
We've modified the way crowd animations are setup per avatar type. If you had previously customized the Koda Lod Levels property on a derived class of BPM_ODK_PlayerCharacterBase, you'll need to update your usage.
How to fix it?
Create a new DA from BPDA_LODLevelsByAvatar
Set your existing BPDA_LODLevels properties per avatar
Set your new DA as the default instance on your derived BPM_ODK_PlayerCharacterBase class
How to test it?
Validate that your characters use the correct ABP content when being rendered in the crowd
Date of change: 18/05/2025
Affected Features: PIE session
What’s broken and why?
The way you login needs to be updated to use our new authentication system
How to fix it?
For any existing project, you'll need to modify your authentication settings. 1. Open to Editor Preferences -> General: Sign In Settings 2. Expand Per Client Sign In Settings 3. Modify each client's sign-in settings to: "Custom" : "https://o7e.preview.msquared.io/api/editor/login" 4. Restart your editor
How to test it?
Start a PIE session and ensure your client can connect to your local deployment.
Date of change: 15/05/2025
Affected Features: Player Character
What’s broken and why?
We moved some of our jump logic inside a component to manage jumping.
How to fix it?
Interface with the new component to enable and disable jumping.
Date of change: 14/05/2025
Affected Features: Text Chat
What’s broken and why?
We have updated text chat in the ODK to use a purely unreal base solutions.
How to fix it?
You will need to add WBP_ODK_TextChat to your HUD to use the text chat functionality.
How to test it?
Test the newly added widget works in game.
Date of change: 13/05/2025
Affected Features: Persistence
What’s broken and why?
The persistence world service has had it's API updated.
How to fix it?
Simply update your blueprint logic to use the new API. The biggest change is that now after registering interst in a value, you will not have the callback executed with the current value. You should use the read API after subscribing to get the initial value.
How to test it?
Ensure your persistence logic is still working.
Date of change: 02/05/2025
Affected Features: Vending Machines
What’s broken and why?
Removed the user collection vending machines. New vending machines should use the overlay rather than in game UI. An example of the overlay vending machine is in the new Boneyard Template.
How to fix it?
Not necessarily something you can fix. Vending machines would have to be redesigned from scatch.
Date of change: 18/03/2025
Affected Features: Various
What’s broken and why?
There are a number of redundant assets in the ODK we are removing. Downstream projects may be using these and could potentially be affected.
List of assets: - BPMC_PlayerTags - BPMC_PickupManager now move to the base template from the ODK plugin
How to fix it?
This probably wont affect anyone. If it does reach out to your Yuga Representative with any problems.
Date of change: 17/03/2025
Affected Features: Interaction System
What’s broken and why?
We wanted to revist the interaction system and make sure it was up to date. Some configuration properties on interactable component may need updating.
How to fix it?
The only thing that should be broken are the configuration properties on the interactable component.
How to test it?
Reconfigure the configuration properties on the interactable component if needed.
Date of change: 26/02/2025
Affected Features: Selfie Camera
What’s broken and why?
Wanted to refactor the selfie cam to make some paths simpler. If you have tinkered with the core selfie cam classes (which you likely have not) you may be affected. How we structure data regarding scanned objects in screenshots has changed. This will affect anyone attempting to read this data.
How to fix it?
Reach out to your Yuga Representative with any problems.
Date of change: 21/02/2025
Affected Features: Widget Handler and Persistence Manager
What’s broken and why?
We are moving these system over to use the "World Service" pattern that was not available when first created. If you are using BP_PersistenceManager or BPC_ODK_WidgetHandlerComponent directly, you will run into issue.
How to fix it?
Instead of using BP_PersistenceManager, you can now call GetPersistenceWorldService from BPFL_ODK_PersistenceWorldService. The returned world service should have the same API as the old BP_PersistenceManager. If you were using BPC_ODK_WidgetHandlerComponent, you should instead use BPFL_ODK_WidgetHandler. It has a similar API that should be easy to move over.
Date of change: 26/02/2025
Affected Features: Core player classes updated to remove BP_Origin_PlayerCharacter , BPM_Origin_PlayerCharacter , BP_M2_PlayerCharacterBase , BPM_M2_PlayerCharacterBase, BP_PlayerController , BP_ODK_PlayerControllerBase, J_CharacterBase and JM_CharacterBase from our hierarchy.
What’s broken and why?
To provide a less opinionated, and more streamlined base ODK experience, and in conjunction with M2, we've deprecated some content that was consider superfluous for ODK purposes. For the purposes of this change, these include the player character classes mentioned above. Any class that derives from the BP_ODK_PlayerCharacterBase ,BPM_ODK_PlayerCharacterBase , BP_M2_PlayerCharacterBase , BPM_M2_PlayerCharacterBase, J_CharacterBase , JM_CharacterBase , BP_ODK_PlayerControllerBase, BP_PlayerController or interacts with those classes might be impacted. Any content functionality that has been removed from the base hierarchy, you're free to move to your project's character classes.
How to fix it?
Any components or variables that were resident on the classes listed above, that are still in use by your project, will need to be migrated to your base class version of that class. For example if you were using the BPMC_ApproachabilityFollowTarget component in your morpheus actor character class, you'd readd an instance of that component to your project's verison of the class, and then fixup any references. If in doubt, please reach out in support. Any reference to these class needs to be updated. This includes casts and properties.
How to test it?
Run a linter pass on your Blueprint content that confirms all content compiles as expected. Additionally make sure all your game features work as intended.

