# Quick Start Guide

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 ](/odk-docs/odk-plugin)and [templates ](/odk-docs/odk-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:** [Platform Documentation](https://docs.otherside.xyz/platform-documentation/)\
\
**The sections that are partcularily important are**

* [Morpheus Networking](/platform-documentation/creation/unreal-development/getting-started/networking)
* [Differences from Unreal](/platform-documentation/creation/unreal-development/getting-started/differences-in-unreal-development-workflow)
* [Actor Pooling](/platform-documentation/creation/unreal-development/features-and-tutorials/actor-pooling)


# Getting setup with ODK development

Quick-start guide on how to download the ODK and start developing!

Access the[ Dashboard](https://otherside.m2worlds.io/dashboard?download\&project=open-roses-send-489062) download page and click "Install the Launcher".

1. The ODK Launcher should open and start downloading the ODK.
2. 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:

   <figure><img src="/files/U4G4eL9Kf90B7bJHwFAH" alt=""><figcaption><p>ODK Templates View</p></figcaption></figure>
3. If this is your first time developing for the ODK, we recommend you use the "ODK Boneyard Template" as the starting template. Click on the template to create a project from it
4. Give your project a name, and click "Create New Project". The template will download and open automatically.

   <figure><img src="/files/0fXuli9UIrzSTrQIPbE1" alt=""><figcaption><p>The ODK Boneyard Template</p></figcaption></figure>
5. Once open, you're able to use Play-in-Editor to test and develop your experiences!

### Creating an online world

It only takes a quick few steps to create a deployment from your project content that others can join.

1. Click the "Upload Content" button within the editor dashboard

   <figure><img src="/files/M4U8VNmbI7yjw7bH8OPi" alt=""><figcaption></figcaption></figure>
2. Give your world a description, and ensure that the `Boneyard` map is selected. Then click the "Upload" button<br>

   <figure><img src="/files/xDWNlAKun6TnIGpmZjQx" alt=""><figcaption></figcaption></figure>
3. 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<br>

   <figure><img src="/files/kquoVyYZswQFzwS3CK4P" alt=""><figcaption></figcaption></figure>
4. 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

   <figure><img src="/files/5bJYHldWOlNWVs83hTjr" alt=""><figcaption></figcaption></figure>


# Technical Overview

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 the World Builder platform. World Builder documentation is available [here](https://docs.otherside.xyz/platform-documentation/). 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:

* [Morpheus networking](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/getting-started/networking)
* [Difference from native Unreal](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/getting-started/differences-in-unreal-development-workflow)
* [Actor pooling](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/features-and-tutorials/actor-pooling)

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 [ODK plugin](/odk-docs/odk-plugin), which is located within the engine plugins of your ODK Unreal installation

<figure><img src="/files/4GnN5pqmfmvkWyHZZN9G" alt=""><figcaption><p>ODK Plugin Content</p></figcaption></figure>

Please peruse the provided content, and use the [Template Projects](/odk-docs/odk-templates) as means to explore what's available within the ODK.


# FAQ

1. <mark style="color:purple;">**How do I get access to the ODK?**</mark>

*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*](https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/getting-setup-with-odk-development)

2. <mark style="color:purple;">**When setting up a new project I get this error "Error getting space required: Error: Expected string at Index 6"**</mark><br>

<figure><img src="/files/6S8EdkWF0SPbReyD9GPi" alt=""><figcaption></figcaption></figure>

*Please log in with <mark style="color:purple;">**Otherside**</mark>**&#x20;and*** *<mark style="color:red;">**NOT**</mark> Msquared as shown in **I am unable to Login to the ODK Launcher***

3. <mark style="color:purple;">**I am unable to Login to the ODK Launcher**</mark>

*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.*

<figure><img src="/files/o8k0lrcSeDWQgtJDNdbw" alt=""><figcaption></figcaption></figure>

4. <mark style="color:purple;">**I am unable to enter admin mode within the engine.**</mark>

*Please review the* [*breaking changes*](https://docs.otherside.xyz/odk-documentation/documentation/breaking-changes)

*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.*

5. <mark style="color:purple;">**When I open the Launcher it get stuck on the spinner**</mark>

*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:* [*https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/faq/guide-on-clearing-credentials*](https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/faq/guide-on-clearing-credentials)

6. <mark style="color:purple;">**I am unable to sign in via the Editor**</mark>

*Please follow the guide here:* [*https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/faq/guide-on-clearing-credentials*](https://docs.otherside.xyz/odk-documentation/documentation/quick-start-guide/faq/guide-on-clearing-credentials)

*If you continue to encounter sign-in failure, please collect your unreal logs and share them in your ODK support channel.*

7. <mark style="color:purple;">**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.**</mark>

<figure><img src="/files/HaWUyiP7u0031EimRd7S" alt=""><figcaption></figcaption></figure>

*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*

* [ ] *<https://main-o7e.preview.msquared.io/?world=\\[WORLD\\_ID]\\&project=\\[PROJECT\\_ID>]*<br>

*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.*


# Guide on Clearing Credentials

If you are having problems signing in within the Unreal Editor then please follow this guide to try and resolve the issue.

### Step 1

Ensure you are using the custom sign -in method in <mark style="color:purple;">Editor > Sign-In Settings</mark> as shown below, this should be set too: [<mark style="color:purple;">**https://o7e.dev/**</mark>](https://o7e.dev/)

<figure><img src="/files/1aQi5zpcGQmOxugYF3Cj" alt=""><figcaption></figcaption></figure>

### Step 2

Clear the sign in credentials by going <mark style="color:purple;">Tools > Clear Credentials</mark>

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.

### Step 3

Clear your web session by deleting the <mark style="color:purple;">webcache</mark> folders from <mark style="color:purple;">project/Saved Folder</mark>

<figure><img src="/files/G9maYy34M2paKhowWWd5" alt=""><figcaption></figcaption></figure>

After doing this <mark style="color:purple;">Restart the Unreal Editor</mark> if the issue persists please move ontpo Step 4

### Step 4

Close the Unreal editor and open <mark style="color:purple;">Windows Credentials Manager</mark>

<figure><img src="/files/fwsSAzOsLh80DcNPE9IN" alt=""><figcaption></figcaption></figure>

### Step 5

Select <mark style="color:purple;">WIndows Credentials</mark>

<figure><img src="/files/bQBVYQeXF9x6kCcRn078" alt=""><figcaption></figcaption></figure>

### Step 6

Delete any credentials starting with <mark style="color:purple;">**Improbable, M2 or Morpheus**</mark>

Reopen the Unreal Editor and you should be prompted to re sign in, which should resolve your issue


# Unreal Blueprint Docs

Our blueprints are mapped out on this link <https://odk-blueprints.o7e.dev/v10>&#x20;

We extract every blueprint across the Engine and ODK for every ODK release, this gets documented on our blueprint docs and this allows you to explore what blueprint functionality is avaliable.&#x20;


# Breaking Changes

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](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/release-notes) when upgrading versions.

Below is the ODK to MSquared version guide

| ODK Version | MSquared Version |
| ----------- | ---------------- |
| v10.0       | v40.0.0          |
| v9.2        | v38.1.3          |
| v9.1        | v38.1.2          |
| v9.0        | v38.1.2          |
| 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          |


# ODK v10 Upgrade Guide

As with all ODK updates, this version contains breaking changes. This update from ODK v9.4.1 to ODK v10.0 is more involved that usual due to a couple of factors.

1. The delta between these two releases is especially large as we went through a simplification and hardening process of the ODK itself
2. The base M2 engine has been repurposed to focus solely on support the ODK framework

Please have a read over the [breaking changes](/odk-docs/breaking-changes/odk-v10-upgrade-guide/v10.0) list. Additional please read through the following four points, and action them if possible:

1. **Download and install the M2Deprecated plugin**\
   This plugin has accumulated deprecated content from M2 over the past year, and has now been removed fully from the base Unreal engine that ships with ODK v10. It's highly likely however that existing projects might have dependencies on some of that deprecated content, so we'd recommend that as a first step that you download the M2Deprecated plugin. You can do that directly from the ODK launcher.\
   \
   ![](/files/PA609fS7jCfO1hcHUrtP)\
   \
   Once downloaded and installed within your project, you'll see it as a project plugin within your content folder.\
   \
   ![](/files/7l3jwkgSpBc2DC79RikN)\
   \
   There's two version of the M2Deprecated plugin, 1.0 and 1.1.\
   1.0 contains the full content pack of all the assets that have been removed from the base M2 engine project. As some of the blueprint content relies on source content that has been fully removed, not all of the blueprint content will compile.\
   1.1 contains a stripped down version of the content that exists in 1.0, with all remaining blueprint content compiling.\
   If you are reliant on missing M2Deprecated content, it's recommend that you:

   1. Add M2Deprecated 1.0 to your project, and attempt to run. If all your content compiles as expected, then you don't need to do anymore.
   2. If you find that some of your content is reliant on blueprints that no longer compile, you can upgrade the M2Deprecated plugin to 1.1, and attempt to run.

   For any further questions about M2Deprecated content, please reach our to your nearest ODK support channel.<br>
2. **Clean up unused config settings**\
   Previously ODK template projects instantiated a lot of required config directly within the config folder. This made it difficult from us to rollout changes to this content, as it was being versioned for the project at the point of creation. To solve this upgrade problem, we've migrated all required, base ODK config into the ODK plugin config folder directly. This means when you take a new ODK plugin, you automatically get migrated to the new required config. This does mean that any existing projects will very likely have redundant, potentially incorrect, config left over within their config folder. It's also likely that you've added your own config to the project config, so it's not as simple as nuking all of it.\
   \
   Our recommended guide for cleaning up is to inspect all config within your config folder, and remove anything you haven't added yourself. In general, the only config we include now as part of the base template instantiation process is the following.<br>

   ```
   <DefaultEngine.ini>
   [/Script/EngineSettings.GameMapsSettings]
   GameDefaultMap=<Path to default map>
   EditorStartupMap=<Path to default map>

   <DefaultGame.ini>
   [/Script/WorldBuilderEditor.M2_WorldBuilderSettings]
   !Maps=ClearArray
   +Maps=(IncludeMapInUpload=True,Map="<Path to default map>",DisplayName="<Name>",Description="<Description>",PictureUrl="")
   ```

   <br>
3. **Base singleton cleanup**\
   We've retired a number of legacy singleton objects that were previously defined within the world settings. The singletons references within world settings can become stale when their class no longer exists, and the bootflow process can stall when attempting to startup the world.\
   Please see this [breaking change](/odk-docs/breaking-changes/odk-v10-upgrade-guide/v10.0#default-required-singleton-classes-reset) for more information and recommend fixup steps.<br>
4. **Roles system refactor**\
   The roles system has gone through a substantial refactor and cleanup, to remove superfluous functionality and better target what we require within the ODK. Please read the [breaking change](/odk-docs/breaking-changes/odk-v10-upgrade-guide/v10.0#roles-component-refactor) log here and ensure that if you require roles for your project, that you've created a new role DT and set it (either project wide within your config, or within your map directly).


# v10.0

This page details the breaking changes included as part of the ODK v10.0 update. Please also ensure to check the breaking changes of M2 v40.0 \[todo: add link] (which this release is based off)

#### Removed ProfileShowAllTabs

Date of change: 12/05/2026

Affected Features: ODK overlay

What’s broken and why?

We no longer support custom visibility tabs within the overlay. Behavior now defaults to that of `ProfileShowAllTabs` being permanently set to true. If you have a use case for displaying only a custom subset of overlay tabs, please reach out to your nearest ODK liaison.

How to fix it?

Delete any override references to `ProfileShowAllTabs` within your live config overrides.

#### Tidy up BP\_ODK\_NameplateWorldService

Date of change: 11/05/2026

Affected Features: Nameplates

What’s broken and why?

Have taken the opportunity to tidy up the `BP_ODK_NameplateWorldService`.\
\
\- The event dispatcher name has been updated to `OnNameplateVisibilityUpdated`.\
\- `RequestHideNameplate` and `RequestShowNameplate` have been renamed `RequestHideNameplates` and `RequestShowNameplates`.\
\- `GetShowNameplate` has been renamed `GetNameplateVisibility`.\
\- Has `GetValidatedBlockingStack` been removed as it is now redundant.\
\- `StackUpdated` has been removed as it is now redundant.

How to fix it?

Use the renamed functions.

#### Removed photo album assets

Date of change: 05/05/2026

Affected Features: Photo Album

What’s broken and why?

We have removed the photo album assets as they are now redundant. The photo album is now part of the web app.

#### Default singletons defined in ini files

Date of change: 30/4/26

Affected Features: Singletons

What’s broken and why?

We have moved where default singletons are defined from the `BP_ODK_BootflowWorldService` to the M2Content.ini file inside the ODK plugin.

How to fix it?

If you have modified a maps `AdditionalSingletons` or `NonMorpheusSingletons` in the world settings you will need to take note of the singletons you have manually added/removed, click the "Reset From Config" button and then add back your noted changes.

#### Default required singleton classes reset

Date of change: 30/04/2026

Affected Features: Singletons

What’s broken and why?

Legacy projects will have some stale singletons within their world settings that no longer exist anymore. These stale singletons can confuse the bootflow process and prevent world startup.

If you see this error then you likely have some bad singleton config:

`LogBootflow: Warning: Bootflow extra info: Waiting for singletons`

How to fix it?

If you see empty entries within your world settings bootflow settings, please remove them:

<figure><img src="/files/SArQr99MQbUdFHAW5uMS" alt=""><figcaption></figcaption></figure>

If you haven't manually added any singletons yourself, then you can use the "Reset To Config" button to reset your world to the default recommended singleton list

<figure><img src="/files/YWV1Rh7J9TAvwR8gIpem" alt=""><figcaption></figcaption></figure>

####

#### Removed Deprecated/Unused files

Date of change: 28/04/2026

Affected Features: Currency, Bootflow, Web Services, UI

What’s broken and why?

We have made a pass at cleaning up deprecated, unused files from the codebase. The list of areas is as follows:

* Currency/Purchaseables (including the `J_CurrencyTypePrimaryAsset` and the `M2_PurchaseablePrimaryAsset`)
* Activities System & Effect Applicators (including the `J_ApplicatorEffectPrimaryAsset`)
* The "prop spawner component" (`J_PropClassPrimaryAsset`)
* Tutorials prompts
* User Data Store and Web Platform Key Value Store (should now use the KV Store World Service)
* Deprecated bootflow steps
* Deprecated Http/Json helpers
* Deprecated Morpheus Actor and Combat functions
* "Timed interaction" helper functions

How to fix it?

Beyond the features no longer being present (if you were using these assets, they will need to be removed), you may also hit errors if you added the removed primary asset types in your asset manager. (Go to `Project Settings -> Game -> Asset Manager`, and clean up any `Primary Asset Types to Scan` that refer to the above classes). If you did not change settings here, you can safely ignore this:

How to test it?

Your project should run without new errors.

#### Sentinel API Changes

Date of change: 27/04/2026

Affected Features: Sentinels

What’s broken and why?

We are introducing worker clients to do offloaded gameplay and NPCs and as a result, we are limiting sentinels to do gameplay validation only hence changing their API.

* `SwitchMorpheusAuthorityWithSentinel` node is now gone. Preferred to use `HasSentinelAuthority` instead
* `GetOwningClientConnection` was already deprecated. It has been deleted in this CL. Use `GetAuthoritativeClientConnection` instaed
* `GetDedicatedSentinelConnection` helper removed. Use `GetSentinelConnection` and cast to a `DedicatedSentinelConnection` instead. Server only as before.

How to fix it?

See above.

#### BPM\_ControlPanelBase default visibility change

Date of change: 28/04/2028

Affected Features: Control panels

What’s broken and why?

The `HidePanelInGame` instance variable within `BPM_ControlPanelBase` has been flipped to true to default hide control panels in game. Control panels are typically only used by elevated roles, and to prevent accidentially leaving panels accessible for standard access, we now default hide them.

How to fix it?

For any control panel that you wish to maintain as default visible for all, toggle the `HidePanelInGame` instance variable on an instances.

#### Removal of Redirectors >6months old

Date of change: 08/04/2026

Affected Features: Anything that relied on the redirectors

What’s broken and why?

Assets that relied on a redirector thats been remove may no longer load or have errors in the BP.

How to fix it?

After identifying erroneous asset

Option 1:

* Open the project in the previous release editor.
* Located each asset and run 'Update Redirector References' (found by r-clicking on its parent folder in the Content Drawer).

Option 2:

* Re-add the redirectors in Config\DefaultEngine.ini under \[CoreRedirects] (list below)
* Located each asset and run 'Update Redirector References' (found by r-clicking on its parent folder in the Content Drawer).

How to test it?

Run editor, loads all your maps and attempt to run 'Play in Editor'.

Cooking the maps will also log errors (and fail the cook) if any asset is broken.

<details>

<summary>Redirectors</summary>

+ClassRedirects=(OldName="/Script/JunoModularCharactersTests.J\_ModularCharactersTestGymActor",NewName="/Script/M2Content.M2\_ModularCharactersTestGymActor") +ClassRedirects=(OldName="/JunoTownhall/UI/Core/HUDLayers/WBP\_TH\_HUDLayer.WBP\_TH\_HUDLayer",NewName="/M2Content/UI/Core/HUDLayers/WBP\_HUDLayer.WBP\_HUDLayer",MatchWildcard=false) +ClassRedirects=(OldName="/JunoTownhall/UI/Core/HUDLayers/WBP\_TH\_OverlayHUDLayer.WBP\_TH\_OverlayHUDLayer",NewName="/M2Content/UI/Core/HUDLayers/WBP\_OverlayHUDLayer.WBP\_OverlayHUDLayer",MatchWildcard=false) +ClassRedirects=(OldName="/Script/M2Content.J\_HUD",NewName="/Script/M2Content.M2\_HUDLayer") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_HUD",NewName="/Script/M2Content.M2\_HUDLayer") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingAsset",NewName="/Script/M2Content.J\_SettingAsset") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingExecutor",NewName="/Script/M2Content.J\_SettingExecutor") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingEntry",NewName="/Script/M2Content.J\_SettingEntry") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingGroup",NewName="/Script/M2Content.J\_SettingGroup") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingWidget",NewName="/Script/M2Content.J\_SettingWidget") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingControl",NewName="/Script/M2Content.J\_SettingControl") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingSliderControl",NewName="/Script/M2Content.J\_SettingSliderControl") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingToggleControl",NewName="/Script/M2Content.J\_SettingToggleControl") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingsList",NewName="/Script/M2Content.J\_SettingsList") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SettingsPanel",NewName="/Script/M2Content.J\_SettingsPanel") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_ChatDisplay",NewName="/Script/M2Content.J\_ChatDisplay") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_ChatEntry",NewName="/Script/M2Content.J\_ChatEntry") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_ChatMessageIndicator",NewName="/Script/M2Content.J\_ChatMessageIndicator") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_ToggleSetting",NewName="/Script/M2Content.J\_ToggleSetting") +ClassRedirects=(OldName="/Script/JunoTownhall.J\_SliderSetting",NewName="/Script/M2Content.J\_SliderSetting") +ClassRedirects=(OldName="/Script/M2Content.J\_PurchaseableSubsystem",NewName="/Script/M2Content.J\_CurrencySubsystem") +ClassRedirects=(OldName="/Script/M2Content.JM\_PurchaseableManager",NewName="/Script/M2Content.M2M\_PurchaseableManager") +ClassRedirects=(OldName="/Script/M2PlayerProfile.M2\_AsyncTaskStorePlayerProfile",NewName="/Script/M2Services.M2\_AsyncTaskStorePlayerProfile") +ClassRedirects=(OldName="/Script/M2PlayerProfile.M2\_PlayerProfileSubsystem",NewName="/Script/M2Services.M2\_PlayerProfileSubsystem") +ClassRedirects=(OldName="/Script/M2PlayerProfile.M2M\_PlayerProfileComponent",NewName="/Script/M2Services.M2M\_PlayerProfileComponent") +ClassRedirects=(OldName="/Script/M2PlayerProfile.M2\_UserIDReplicationComponent",NewName="/Script/M2Services.M2\_UserIDReplicationComponent") +ClassRedirects=(OldName="/Script/JunoModeration.J\_ModerationSubsystem",NewName="/Script/M2Services.M2\_ModerationSubsystem") +StructRedirects=(OldName="/Script/JunoTownhall.J\_SettingData",NewName="/Script/M2Content.J\_SettingData") +StructRedirects=(OldName="/Script/JunoTownhall.ToggleValue",NewName="/Script/M2Content.ToggleValue") +EnumRedirects=(OldName="/Script/JunoTownhall.EJ\_EntryType",NewName="/Script/M2Content.EJ\_EntryType") +PackageRedirects=(OldName="/JunoTownhall/Core/AI/...",NewName="/M2Content/AI/",MatchWildcard=true) +StructRedirects=(OldName="/Game/Developers/alexmoon/AnimModifires/FeetAnimationModifierBP/BoneModifierST",NewName="/M2Content/Animations/AnimModifires/FeetAnimationModifierBP/BoneModifierST") +PackageRedirects=(OldName="/Game/Developers/alexmoon/AnimModifires/FeetAnimationModifierBP/BoneModifierST",NewName="/M2Content/Animations/AnimModifires/FeetAnimationModifierBP/BoneModifierST") +PackageRedirects=(OldName="/Game/Developers/alexmoon/AnimModifires/FeetAnimationModifierBP/FeetAnimationModifierBP",NewName="/M2Content/Animations/AnimModifires/FeetAnimationModifierBP/FeetAnimationModifierBP") +PackageRedirects=(OldName="/Game/Developers/alexmoon/AnimModifires/AnimModify\_Rotation",NewName="/M2Content/Animations/AnimModifires/AnimModify\_Rotation") +PackageRedirects=(OldName="/Game/Developers/alexmoon/AnimModifires/FeetAnimationModifierBP/RootMotionDirections",NewName="/M2Content/Animations/AnimModifires/FeetAnimationModifierBP/RootMotionDirections") +PackageRedirects=(OldName="/Game/Developers/AriPanzer/MirroringProfile\_UE5Mannequin",NewName="/M2Content/Animations/MirroringProfile\_UE5Mannequin") +PackageRedirects=(OldName="/JunoGeneral/UI/Skin/...",NewName="/M2Content/UI/Skin/",MatchWildcard=true) +EnumRedirects=(OldName="/Script/JunoTownhall.EUIModeContext",NewName="/Script/M2Content.EUIModeContext") +PackageRedirects=(OldName="/JunoTownhall/Core/EUIModeContext",NewName="/M2Content/UI/Core/EUIModeContext") +PropertyRedirects=(OldName="/Script/M2Content.J\_SettingsPanel.SettingGroupClass",NewName="/Script/M2Content.J\_SettingPanel.DefaultSettingsGroupClass") +PropertyRedirects=(OldName="/Script/M2Content.J\_SettingsPanel.SettingCategoryClass",NewName="/Script/M2Content.J\_SettingPanel.DefaultSettingsCategoryClass") +PropertyRedirects=(OldName="/Script/M2Content.J\_SettingsPanel.SettingsListClass",NewName="/Script/M2Content.J\_SettingPanel.DefaultSettingsListClass") +PropertyRedirects=(OldName="/M2Content/Audio/System/Music/BP\_Audio\_MusicController.BP\_Audio\_MusicControllerComponent",NewName="/M2Content/Audio/System/Music/BP\_Audio\_MusicController.BPC\_Audio\_MusicController") +PropertyRedirects=(OldName="/Script/M2Content.M2M\_PlayerSpawnerSingleton.PlayerMorpheusActor",NewName="/Script/M2Content.M2M\_PlayerSpawnerSingleton.PlayerMorpheusActorClass") +PropertyRedirects=(OldName="/Script/M2Content.M2\_SplashScreenImage.bIsClickable",NewName="/Script/M2Content.M2\_SplashScreenImage.bForceClickToSkip") +FunctionRedirects=(OldName="/Script/M2Content.J\_EquipmentQuickbarWidget.NotifyEquipmentUpdated",NewName="/Script/M2Content.M2\_ItemQuickbarWidget.NotifyQuickbarSlotsUpdated") +FunctionRedirects=(OldName="/Script/M2Content.M2\_PlaceModeQuickbarWidget.NotifyPlacementItemsUpdated",NewName="/Script/M2Content.M2\_ItemQuickbarWidget.NotifyQuickbarSlotsUpdated") +ClassRedirects=(OldName="/Script/JunoGeneral.J\_GameInstance",NewName="/Script/M2Content.M2\_GameInstance") +ClassRedirects=(OldName="/Script/JunoGeneral.J\_GameMode",NewName="/Script/M2Content.M2\_GameMode") +ClassRedirects=(OldName="/Script/JunoGeneral.J\_GameState",NewName="/Script/M2Content.M2\_GameState") +ClassRedirects=(OldName="/Script/M2Content.J\_GameInstance",NewName="/Script/M2Content.M2\_GameInstance") +ClassRedirects=(OldName="/Script/M2Content.J\_GameMode",NewName="/Script/M2Content.M2\_GameMode") +ClassRedirects=(OldName="/Script/M2Content.J\_GameState",NewName="/Script/M2Content.M2\_GameState") +ClassRedirects=(OldName="/Script/M2Content.JM\_PlayerInitObj",NewName="/Script/M2Content.M2M\_PlayerSpawnerSingleton") +ClassRedirects=(OldName="/Script/M2Content.J\_SingletonSpawnerComponent",NewName="/Script/M2Content.M2\_SingletonSpawnerComponent") +PropertyRedirects=(OldName="/M2Content/Core/BP\_M2\_PlayerCharacterBase.BP\_TH\_CharacterMoveSpeedComponent",NewName="/M2Content/Core/BP\_M2\_PlayerCharacterBase.BPC\_CharacterMoveSpeedComponent") +PropertyRedirects=(OldName="J\_EmotePrimaryAsset.EmoteSoundBase",NewName="J\_EmotePrimaryAsset.EmoteSound") +PropertyRedirects=(OldName="J\_EmotePrimaryAsset.SuperEmoteSoundBase",NewName="J\_EmotePrimaryAsset.SuperEmoteSound") +StructRedirects=(OldName="/Script/M2ContentEditor.M2AnimNode\_PhizMocapPose",NewName="/Script/M2ContentEditor.M2AnimNode\_MocapPose") +ClassRedirects=(OldName="/Script/M2ContentEditor.M2AnimGraphNode\_PhizMocapPose",NewName="/Script/M2ContentEditor.M2AnimGraphNode\_MocapPose") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.AddItemNameToEquipment",NewName="/Script/M2Content.JM\_EquipmentComponent.AddItemNameToAssignmentSlots") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.GetLoadedEquipmentAssets",NewName="/Script/M2Content.JM\_EquipmentComponent.GetLoadedItems") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.AddItemKeyToEquipment",NewName="/Script/M2Content.JM\_EquipmentComponent.AddItemKeyToAssignmentSlots") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.GetEquipmentSlotItemKeys",NewName="/Script/M2Content.JM\_EquipmentComponent.GetSlotItemKeys") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.UnequipItemByKey",NewName="/Script/M2Content.JM\_EquipmentComponent.UnassignItemByKey") +FunctionRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.GetEquippedItems",NewName="/Script/M2Content.JM\_EquipmentComponent.GetAssignedItems") +PropertyRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.OnEquipmentLoaded",NewName="/Script/M2Content.JM\_EquipmentComponent.OnItemsLoaded") +PropertyRedirects=(OldName="/Script/M2Content.JM\_EquipmentComponent.OnEquipmentUpdated",NewName="/Script/M2Content.JM\_EquipmentComponent.OnItemsUpdated") +FunctionRedirects=(OldName="/Script/M2Content.J\_ItemExecutorBase.NotifyAbilityActivated",NewName="/Script/M2Content.J\_ItemExecutorBase.NotifyAbilityFinished") +FunctionRedirects=(OldName="/Script/M2Content.M2\_ItemVisualsBase.TearDown\_BP",NewName="/Script/M2Content.M2\_ItemVisualsBase.TearDown\_Event") +StructRedirects=(OldName="/Script/M2Content.J\_ItemToggleProperty",NewName="/Script/M2Content.M2\_ItemToggleProperty") +ClassRedirects=(OldName="/Script/M2Content.M2\_InventoryMetadataComponent",NewName="/Script/M2Content.M2M\_InventoryMetadataComponent") +PropertyRedirects=(OldName="M2\_ItemQuickbarWidget.QuickbarComponentClass", NewName="M2\_ItemQuickbarWidget.QuickbarViewClass") +ClassRedirects=(OldName="/Script/M2Content.M2\_PlaceModeQuickbarWidget",NewName="/Script/M2Content.M2\_QuickbarWrapperWidget") +PackageRedirects=(OldName="/M2Content/Props/BPM\_WeatherControls",NewName="/M2Content/Core/TimeOfDay/BPM\_TimeOfDayControls") +ClassRedirects=(OldName="/M2Content/Props/BPM\_WeatherControls",NewName="/M2Content/Core/TimeOfDay/BPM\_TimeOfDayControls") +PackageRedirects=(OldName="/M2Content/Maps/ExampleMap/...",NewName="/M2Example/Maps/",MatchWildcard=true) +ClassRedirects=(OldName="/M2Content/Maps/ExampleMap/...",NewName="/M2Example/Maps/",MatchWildcard=true) +PackageRedirects=(OldName="/M2Content/AI/BTT\_StoreSuperEmoteIndex",NewName="/M2Content/AI/BTT\_StoreSuperEmoteName") +ClassRedirects=(OldName="/M2Content/AI/BTT\_StoreSuperEmoteIndex.BTT\_StoreSuperEmoteIndex\_C",NewName="/M2Content/AI/BTT\_StoreSuperEmoteName.BTT\_StoreSuperEmoteName\_C") +FunctionRedirects=(OldName="/Script/M2Content.M2\_CharacterInhibitorComponent.OnCaptureHold",NewName="/Script/M2Content.M2\_CharacterInhibitorComponent.QueueReleaseFromCapture") +FunctionRedirects=(OldName="/Script/M2Content.M2\_CharacterInhibitorComponent.OnCaptureRelease",NewName="/Script/M2Content.M2\_CharacterInhibitorComponent.ReleaseFromCapture") +FunctionRedirects=(OldName="/Script/M2Content.M2\_CharacterCaptureComponent.Hold",NewName="/Script/M2Content.M2\_CharacterCaptureComponent.QueueRelease") +EnumRedirects=(OldName="/Script/M2Content.EM2\_CharacterCaptureState",ValueChanges=(("ReleaseQueued","QueuedForRelease"))) +PackageRedirects=(OldName="/M2Content/UI/Settings/SettingAssets...",NewName="/M2Content/UI/Settings/SettingData",MatchWildcard=true) +PackageRedirects=(OldName="/M2Content/UI/Settings/WBP\_Settings",NewName="/M2Content/UI/Settings/WBP\_M2\_Settings") +PropertyRedirects=(OldName="/Script/M2Content.J\_PawnSetAsset.BasePawn",NewName="/Script/M2Content.J\_PawnSetAsset.OverrideAuthPawnClass") +PropertyRedirects=(OldName="/Script/M2Content.M2M\_AvatarShapeRulesComponent.ShootableChannel",NewName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent.InteractionQueryChannel") +ClassRedirects=(OldName="/Script/M2Content.M2M\_AvatarShapeRulesComponent",NewName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent") +PropertyRedirects=(OldName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent.ShootableQueryChannel",NewName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent.InteractionQueryChannel") +PropertyRedirects=(OldName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent.ShootableColliderChannel",NewName="/Script/M2Content.M2M\_AvatarPhysicsAssetComponent.InteractionColliderChannel") +FunctionRedirects=(OldName="/Script/M2Content.J\_BootflowSubsystem.HandleAuthActorSpawned",NewName="/Script/M2Content.J\_BootflowSubsystem.HandleAuthActorSpawned\_JM") +FunctionRedirects=(OldName="/Script/M2Content.J\_BootflowSubsystem.HandleAuthPawnSpawned",NewName="/Script/M2Content.J\_BootflowSubsystem.HandleAuthPawnSpawned\_JM") +FunctionRedirects=(OldName="/Script/M2Content.J\_BootflowSubsystem.GetAuthActor",NewName="/Script/M2Content.J\_BootflowSubsystem.GetAuthActor\_JM") +PropertyRedirects=(OldName="/Script/M2Content.M2\_MocapCoordinatorComponent.bShowMoveAiReceiverMesh",NewName="/Script/M2Content.M2\_MocapCoordinatorComponent.bShowMocapReceiverMesh") +PropertyRedirects=(OldName="/Script/M2Content.M2\_MocapCoordinatorComponent.bShowDummyMainMesh",NewName="/Script/M2Content.M2\_MocapCoordinatorComponent.bShowRetargetedPoseMesh") +PropertyRedirects=(OldName="/Script/M2Content.M2\_MocapCoordinatorComponent.MoveAiReceiverAnimClass",NewName="/Script/M2Content.M2\_MocapCoordinatorComponent.MocapReceiverAnimClass") +PackageRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPRoleDataProvider",NewName="/M2Content/WebPlatform/BP\_M2\_RoleDataProvider") +ClassRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPRoleDataProvider.BP\_M2\_WPRoleDataProvider\_C",NewName="/M2Content/WebPlatform/BP\_M2\_RoleDataProvider.BP\_M2\_RoleDataProvider\_C") +PackageRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPProfileDataProvider",NewName="/M2Content/WebPlatform/BP\_M2\_ProfileDataProvider") +ClassRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPProfileDataProvider.BP\_M2\_WPProfileDataProvider\_C",NewName="/M2Content/WebPlatform/BP\_M2\_ProfileDataProvider.BP\_M2\_ProfileDataProvider\_C") +PackageRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPKeyValueStoreService",NewName="/M2Content/WebPlatform/BP\_M2\_KVStoreService") +ClassRedirects=(OldName="/M2Content/WebPlatform/BP\_M2\_WPKeyValueStoreService.BP\_M2\_WPKeyValueStoreService\_C",NewName="/M2Content/WebPlatform/BP\_M2\_KVStoreService.BP\_M2\_KVStoreService\_C") +StructRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadItem.SM2\_WP\_KvStoreReadItem",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadItem.S\_M2\_KvStoreReadItem") +ObjectRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadItem.SM2\_WP\_KvStoreReadItem",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadItem.S\_M2\_KvStoreReadItem") +PackageRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadItem",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadItem") +StructRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadKey.SM2\_WP\_KvStoreReadKey",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadKey.S\_M2\_KvStoreReadKey") +ObjectRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadKey.SM2\_WP\_KvStoreReadKey",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadKey.S\_M2\_KvStoreReadKey") +PackageRedirects=(OldName="/M2Content/WebPlatform/ApiModels/SM2\_WP\_KvStoreReadKey",NewName="/M2Content/WebPlatform/ApiModels/S\_M2\_KvStoreReadKey") +StructRedirects=(OldName="/M2Content/WebPlatform/SM2\_PendingProfileQuery.SM2\_PendingProfileQuery",NewName="/M2Content/WebPlatform/S\_M2\_PendingProfileQuery.S\_M2\_PendingProfileQuery") +ObjectRedirects=(OldName="/M2Content/WebPlatform/SM2\_PendingProfileQuery.SM2\_PendingProfileQuery",NewName="/M2Content/WebPlatform/S\_M2\_PendingProfileQuery.S\_M2\_PendingProfileQuery") +PackageRedirects=(OldName="/M2Content/WebPlatform/SM2\_PendingProfileQuery",NewName="/M2Content/WebPlatform/S\_M2\_PendingProfileQuery") +StructRedirects=(OldName="/M2Content/WebPlatform/SM2\_UserContext.SM2\_UserContext",NewName="/M2Content/WebPlatform/S\_M2\_UserContext.S\_M2\_UserContext") +ObjectRedirects=(OldName="/M2Content/WebPlatform/SM2\_UserContext.SM2\_UserContext",NewName="/M2Content/WebPlatform/S\_M2\_UserContext.S\_M2\_UserContext") +PackageRedirects=(OldName="/M2Content/WebPlatform/SM2\_UserContext",NewName="/M2Content/WebPlatform/S\_M2\_UserContext") +PackageRedirects=(OldName="/M2Content/WorldBuilderLaunch/WBC\_WBL\_StatusDisplayInterface",NewName="/M2Content/WorldBuilderLaunch/BPI\_WBL\_StatusDisplayInterface") +ClassRedirects=(OldName="/M2Content/WorldBuilderLaunch/WBC\_WBL\_StatusDisplayInterface.WBC\_WBL\_StatusDisplayInterface\_C",NewName="/M2Content/WorldBuilderLaunch/BPI\_WBL\_StatusDisplayInterface.BPI\_WBL\_StatusDisplayInterface\_C") +FunctionRedirects=(OldName="/Script/M2Content.M2\_NotificationsFunctionLibrary.RemoveNotificationsById",NewName="/Script/M2Content.M2\_NotificationsFunctionLibrary.RemoveActiveNotificationById") +FunctionRedirects=(OldName="/Script/M2Content.M2M\_NotificationsSingleton.RemoveNotificationsById",NewName="/Script/M2Content.M2M\_NotificationsSingleton.RemoveActiveNotificationById") +PackageRedirects=(OldName="/M2Unreal/...",NewName="/M2Core/",MatchWildcard=true) +ClassRedirects=(OldName="/Script/M2Unreal...",NewName="/Script/M2Core.",MatchWildcard=true) +ClassRedirects=(OldName="/Script/JunoCoreUI.J\_HUDLayer",NewName="/Script/JunoCoreUI.J\_HUDLayerBase") +StructRedirects=(OldName="/Script/M2Unreal...",NewName="/Script/M2Core.",MatchWildcard=true) +ClassRedirects=(OldName="/Script/JunoAvatars.J\_AvatarSchemeSet",NewName="/Script/JunoModularCharacters.J\_AvatarSchemeSet") +PackageRedirects=(OldName="/Script/JunoAvatars",Removed=true) +EnumRedirects=(OldName="EJ\_InteractionPromptType",ValueChanges=(("MultipleButtons","BasicMenu"))) +PackageRedirects=(OldName="/Script/M2TextChat",NewName="/Script/M2Social") +ClassRedirects=(OldName="/Script/M2Social.M2\_TextChatComponent",NewName="/Script/M2Social.M2M\_TextChatComponent") +ClassRedirects=(OldName="/Script/M2TextChat.M2\_TextChatComponent",NewName="/Script/M2Social.M2M\_TextChatComponent") +ClassRedirects=(OldName="/Script/JunoCoreUI.M2\_StandardButton",NewName="/Script/JunoCoreUI.M2\_StandardButtonWidgetBase") +ClassRedirects=(OldName="/Script/JunoCoreUI.M2\_StandardButtonMenu",NewName="/Script/JunoCoreUI.M2\_StandardButtonListWidget") +ClassRedirects=(OldName="/Script/JunoCoreUI.M2\_StandardGroupEntryButton",NewName="/Script/JunoCoreUI.M2\_StandardGroupEntryButtonWidget") +ClassRedirects=(OldName="/Script/JunoCoreUI.M2\_StandardButtonMenuWidget",NewName="/Script/JunoCoreUI.M2\_StandardButtonListWidget") +FunctionRedirects=(OldName="/Script/JunoCoreUI.M2\_StandardButtonBaseData.Initialize",NewName="/Script/JunoCoreUI.M2\_StandardButtonBaseData.InitializeOnClient") +ClassRedirects=(OldName="/Script/M2ObjectInteraction.M2\_LoSTargetingStrategy",NewName="/Script/M2ObjectInteraction.M2\_ScreenCenterPriorityTargetingStrategy") +EnumRedirects=(OldName="/Script/M2ObjectInteraction.EM2\_InteractionCloseMenuOnSelectionOverride",NewName="/Script/M2ObjectInteraction.EM2\_InteractionOverride") +FunctionRedirects=(OldName="/Script/M2ObjectInteraction.M2\_InteractableComponent.AddNonParentOption",NewName="/Script/M2ObjectInteraction.M2\_InteractableComponent.AddInteractionOption") +FunctionRedirects=(OldName="/Script/M2ObjectInteraction.M2\_InteractableComponent.RemoveNonParentOption",NewName="/Script/M2ObjectInteraction.M2\_InteractableComponent.RemoveInteractionOption") +PropertyRedirects=(OldName="M2\_StandardButtonBaseData.Icon",NewName="IconLeft1") +StructRedirects=(OldName="/Script/M2Tags.M2\_UserTagPlayers",NewName="/Script/M2Tags.M2\_MorpheusActorSet") +PropertyRedirects=(OldName="J\_SkinPrimaryAsset.SkinSettings",NewName="J\_SkinPrimaryAsset.CustomSkinSettings"B) +FunctionRedirects=(OldName="/Script/M2ObjectInteraction.M2\_InteractableHandlerComponent.GetCurrentMousedOverInteractable",NewName="/Script/M2ObjectInteraction.M2\_InteractableHandlerComponent.GetCurrentMousedOverInteractables") +ClassRedirects=(OldName="/Script/M2ObjectInteraction.M2\_TargetingStrategy",NewName="/Script/M2ObjectInteraction.M2\_InteractionTargetingStrategy") +ClassRedirects=(OldName="/Script/M2ObjectInteraction.M2\_DefaultTargetingStrategy",NewName="/Script/M2ObjectInteraction.M2\_CursorLineTraceTargetingStrategy") +PropertyRedirects=(OldName="/Script/M2ObjectInteraction.M2\_InteractableHandlerComponent.OnInteractionRequested",NewName="/Script/M2ObjectInteraction.M2\_InteractableHandlerComponent.OnOptionSelected") +ClassRedirects=(OldName="/Script/JunoCoreUI.J\_JunoCoreWidget",NewName="/Script/JunoCoreUI.M2\_CoreWidget") +FunctionRedirects=(OldName="/Script/JunoModularCharacters.JM\_ModularCharacterComponent.LoadInteropCharacterFromProfile",NewName="/Script/JunoModularCharacters.JM\_ModularCharacterComponent.LoadAvatarFromM2Profile") +StructRedirects=(OldName="/Script/JunoUtils.M2\_JsonValueWrapper",NewName="/Script/M2HttpUtilities.M2\_JsonValueWrapper") +ClassRedirects=(OldName="/Script/JunoUtils.M2\_JsonFunctionLibrary",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary") +ClassRedirects=(OldName="/Script/JunoUtils.M2\_DownloadJsonNode",NewName="/Script/M2HttpUtilities.M2\_DownloadJsonNode") +ClassRedirects=(OldName="/Script/JunoUtils.M2\_K2Node\_JsonObjectToStruct",NewName="/Script/M2HttpUtilities.M2\_K2Node\_JsonObjectToStruct") +EnumRedirects=(OldName="/Script/JunoUtils.M2\_JsonValueType",NewName="/Script/M2HttpUtilities.M2\_JsonValueType")) +PropertyRedirects=(OldName="J\_ModularCharacterComponent.OnMeshUpdated\_BP",NewName="J\_ModularCharacterComponent.OnBaseMeshUpdated") +FunctionRedirects=(OldName="/Script/JunoModularCharacters.JM\_ModularCharacterComponent.ResetSkeleton",NewName="/Script/JunoModularCharacters.JM\_ModularCharacterComponent.SetPostProcessAnimBlueprint") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueNull",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsNull") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueInteger",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsInteger") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueNumber",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsNumber") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueBoolean",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsBoolean") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueString",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsString") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueNumberString",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsNumberString") +FunctionRedirects=(OldName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueObject",NewName="/Script/M2HttpUtilities.M2\_JsonFunctionLibrary.GetJsonValueAsObject") +ClassRedirects=(OldName="/Script/JunoUtils.J\_DataAsset",NewName="/Script/JunoUtils.M2\_DataAsset") +FunctionRedirects=(OldName="/Script/JunoCoreUI.M2\_CoreUIFunctionLibrary.WidgetConnectedToHUD",NewName="/Script/JunoCoreUI.M2\_CoreUIFunctionLibrary.IsWidgetAddedToViewport") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BP\_ControlPanelWidget",NewName="/M2Extras\_ControlPanels/BP\_ControlPanelWidget") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BP\_ControlPanelWidget",NewName="/M2Extras\_ControlPanels/BP\_ControlPanelWidget") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPC\_ControlPanelAutoOpener",NewName="/M2Extras\_ControlPanels/BPC\_ControlPanelAutoOpener") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPC\_ControlPanelAutoOpener",NewName="/M2Extras\_ControlPanels/BPC\_ControlPanelAutoOpener") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPC\_ControlPanelBuilder",NewName="/M2Extras\_ControlPanels/BPC\_ControlPanelBuilder") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPC\_ControlPanelBuilder",NewName="/M2Extras\_ControlPanels/BPC\_ControlPanelBuilder") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPFL\_ControlsFunctionLibrary",NewName="/M2Extras\_ControlPanels/BPFL\_ControlsFunctionLibrary") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPFL\_ControlsFunctionLibrary",NewName="/M2Extras\_ControlPanels/BPFL\_ControlsFunctionLibrary") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPI\_ControlValuesProvider",NewName="/M2Extras\_ControlPanels/BPI\_ControlValuesProvider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPI\_ControlValuesProvider",NewName="/M2Extras\_ControlPanels/BPI\_ControlValuesProvider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPM\_ControlPanelBase",NewName="/M2Extras\_ControlPanels/BPM\_ControlPanelBase") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPM\_ControlPanelBase",NewName="/M2Extras\_ControlPanels/BPM\_ControlPanelBase") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPM\_ControlPanelPlayerComponent",NewName="/M2Extras\_ControlPanels/BPM\_ControlPanelPlayerComponent") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPM\_ControlPanelPlayerComponent",NewName="/M2Extras\_ControlPanels/BPM\_ControlPanelPlayerComponent") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPMC\_ControlPanelProvider",NewName="/M2Extras\_ControlPanels/BPMC\_ControlPanelProvider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPMC\_ControlPanelProvider",NewName="/M2Extras\_ControlPanels/BPMC\_ControlPanelProvider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPML\_ControlPanelMacros",NewName="/M2Extras\_ControlPanels/BPML\_ControlPanelMacros") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/BPML\_ControlPanelMacros",NewName="/M2Extras\_ControlPanels/BPML\_ControlPanelMacros") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/BPM\_ControlPanelsHandler",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/BPM\_ControlPanelsHandler") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/BPM\_ControlPanelsHandler",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/BPM\_ControlPanelsHandler") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSet",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSet") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSet",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSet") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSetElement",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSetElement") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSetElement",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSetElement") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSetsMenu",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSetsMenu") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelSetsMenu",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelSetsMenu") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelsHandlerOptions",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelsHandlerOptions") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/ControlPanelHandler/WBP\_ControlPanelsHandlerOptions",NewName="/M2Extras\_ControlPanels/ControlPanelHandler/WBP\_ControlPanelsHandlerOptions") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_AllControlMethodsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_AllControlMethodsExample") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_AllControlMethodsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_AllControlMethodsExample") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ControllablePropertyComponentsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ControllablePropertyComponentsExample") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ControllablePropertyComponentsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ControllablePropertyComponentsExample") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ControlPanel\_WidgetsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ControlPanel\_WidgetsExample") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ControlPanel\_WidgetsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ControlPanel\_WidgetsExample") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ManipulateControlWidgetsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ManipulateControlWidgetsExample") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/BPM\_ManipulateControlWidgetsExample",NewName="/M2Extras\_ControlPanels/Examples/BPM\_ManipulateControlWidgetsExample") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/WBP\_ControlPanelGymInfo",NewName="/M2Extras\_ControlPanels/Examples/WBP\_ControlPanelGymInfo") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/Examples/WBP\_ControlPanelGymInfo",NewName="/M2Extras\_ControlPanels/Examples/WBP\_ControlPanelGymInfo") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/BP\_ControlPresetsSaveGame",NewName="/M2Extras\_ControlPanels/SaveGame/BP\_ControlPresetsSaveGame") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/BP\_ControlPresetsSaveGame",NewName="/M2Extras\_ControlPanels/SaveGame/BP\_ControlPresetsSaveGame") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/WBP\_ControlPresetLoadPreset",NewName="/M2Extras\_ControlPanels/SaveGame/WBP\_ControlPresetLoadPreset") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/WBP\_ControlPresetLoadPreset",NewName="/M2Extras\_ControlPanels/SaveGame/WBP\_ControlPresetLoadPreset") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/WBP\_ControlPresetSavePreset",NewName="/M2Extras\_ControlPanels/SaveGame/WBP\_ControlPresetSavePreset") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/SaveGame/WBP\_ControlPresetSavePreset",NewName="/M2Extras\_ControlPanels/SaveGame/WBP\_ControlPresetSavePreset") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/BPFL\_ControlPanel\_SkinUtilities",NewName="/M2Extras\_ControlPanels/UI/BPFL\_ControlPanel\_SkinUtilities") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/BPFL\_ControlPanel\_SkinUtilities",NewName="/M2Extras\_ControlPanels/UI/BPFL\_ControlPanel\_SkinUtilities") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/BPI\_CP\_WindowContentProvider",NewName="/M2Extras\_ControlPanels/UI/BPI\_CP\_WindowContentProvider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/BPI\_CP\_WindowContentProvider",NewName="/M2Extras\_ControlPanels/UI/BPI\_CP\_WindowContentProvider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Base",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Base") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Base",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Base") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonGroup",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonGroup") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonGroup",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonGroup") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonSequence",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonSequence") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonSequence",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonSequence") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonToggle",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonToggle") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonToggle",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonToggle") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTrigger",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTrigger") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTrigger",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTrigger") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTriggers",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTriggers") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTriggers",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ButtonTriggers") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Checkbox",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Checkbox") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Checkbox",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Checkbox") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_CheckboxFlags",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_CheckboxFlags") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_CheckboxFlags",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_CheckboxFlags") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ColorSwatches",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ColorSwatches") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_ColorSwatches",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_ColorSwatches") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Dropdown",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Dropdown") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Dropdown",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Dropdown") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Slider",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Slider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Slider",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Slider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Spinbox",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Spinbox") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Spinbox",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Spinbox") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TEMPLATECOPYME",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TEMPLATECOPYME") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TEMPLATECOPYME",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TEMPLATECOPYME") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TextboxMultiline",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TextboxMultiline") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TextboxMultiline",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TextboxMultiline") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TextBoxSingleline",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TextBoxSingleline") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_TextBoxSingleline",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_TextBoxSingleline") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Vector",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Vector") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/ControlWidgets/WBP\_CP\_Control\_Vector",NewName="/M2Extras\_ControlPanels/UI/ControlWidgets/WBP\_CP\_Control\_Vector") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Docking/BPI\_DockableWidget",NewName="/M2Extras\_ControlPanels/UI/Docking/BPI\_DockableWidget") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Docking/BPI\_DockableWidget",NewName="/M2Extras\_ControlPanels/UI/Docking/BPI\_DockableWidget") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_Button",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_Button") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_Button",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_Button") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_ButtonGroup",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_ButtonGroup") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_ButtonGroup",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_ButtonGroup") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_EditableText",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_EditableText") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_EditableText",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_EditableText") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_GenericExpandableArea",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_GenericExpandableArea") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_GenericExpandableArea",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_GenericExpandableArea") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_SlateWidgetContainer",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_SlateWidgetContainer") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_SlateWidgetContainer",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_SlateWidgetContainer") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_TemporaryTextMessage",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_TemporaryTextMessage") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_TemporaryTextMessage",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_TemporaryTextMessage") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_TextMessage",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_TextMessage") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/Elements/WBP\_CP\_TextMessage",NewName="/M2Extras\_ControlPanels/UI/Elements/WBP\_CP\_TextMessage") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlContainer",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlContainer") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlContainer",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlContainer") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanel",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanel") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanel",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanel") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanel\_Movable",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanel\_Movable") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanel\_Movable",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanel\_Movable") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanelDock",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanelDock") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanelDock",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanelDock") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanelDockEntry",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanelDockEntry") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_ControlPanelDockEntry",NewName="/M2Extras\_ControlPanels/UI/WBP\_ControlPanelDockEntry") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_Header",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_Header") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_Header",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_Header") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_Label",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_Label") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_Label",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_Label") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_PanelContainer",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_PanelContainer") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/UI/WBP\_CP\_Content\_PanelContainer",NewName="/M2Extras\_ControlPanels/UI/WBP\_CP\_Content\_PanelContainer") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_BoolCheckbox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_BoolCheckbox") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_BoolCheckbox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_BoolCheckbox") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_BoolToggleButton",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_BoolToggleButton") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_BoolToggleButton",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_BoolToggleButton") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonGroup",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonGroup") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonGroup",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonGroup") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonSequence",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonSequence") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonSequence",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_ButtonSequence") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_CheckboxFlags",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_CheckboxFlags") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_CheckboxFlags",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_CheckboxFlags") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_Dropdown",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_Dropdown") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_Dropdown",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_Dropdown") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSlider",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSlider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSlider",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSlider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSpinbox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSpinbox") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSpinbox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_FloatSpinbox") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_IntSlider",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_IntSlider") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_IntSlider",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_IntSlider") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_IntSpinBox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_IntSpinBox") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_IntSpinBox",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_IntSpinBox") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_RGBSwatches",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_RGBSwatches") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_RGBSwatches",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_RGBSwatches") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxMultiLine",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxMultiLine") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxMultiLine",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxMultiLine") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxSingleLine",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxSingleLine") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxSingleLine",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TextBoxSingleLine") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_Trigger",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_Trigger") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_Trigger",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_Trigger") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TriggerGroup",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TriggerGroup") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_TriggerGroup",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_TriggerGroup") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_VectorSpinboxes",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_VectorSpinboxes") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BP\_M2\_WidgetProvider\_VectorSpinboxes",NewName="/M2Extras\_ControlPanels/WidgetProviders/BP\_M2\_WidgetProvider\_VectorSpinboxes") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BPI\_WidgetProvider\_GroupsReceiver",NewName="/M2Extras\_ControlPanels/WidgetProviders/BPI\_WidgetProvider\_GroupsReceiver") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BPI\_WidgetProvider\_GroupsReceiver",NewName="/M2Extras\_ControlPanels/WidgetProviders/BPI\_WidgetProvider\_GroupsReceiver") +PackageRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BPI\_WidgetProvider\_OptionsReceiver",NewName="/M2Extras\_ControlPanels/WidgetProviders/BPI\_WidgetProvider\_OptionsReceiver") +ClassRedirects=(OldName="/M2Core/JunoInteraction/ControlPanel/WidgetProviders/BPI\_WidgetProvider\_OptionsReceiver",NewName="/M2Extras\_ControlPanels/WidgetProviders/BPI\_WidgetProvider\_OptionsReceiver") +PackageRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastScreen\_Base",NewName="/M2Core/Millicast/BP\_MillicastScreen\_Base") +ClassRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastScreen\_Base.BP\_MillicastScreen\_Base\_C",NewName="/M2Core/Millicast/BP\_MillicastScreen\_Base.BP\_MillicastScreen\_Base\_C") +PackageRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastChannel",NewName="/M2Core/Millicast/BP\_MillicastChannel") +ClassRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastChannel.BP\_MillicastChannel\_C",NewName="/M2Core/Millicast/BP\_MillicastChannel.BP\_MillicastChannel\_C") +PackageRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastCapture",NewName="/M2Core/Millicast/BP\_MillicastCapture") +ClassRedirects=(OldName="/M2Core/JunoMillicast/BP\_MillicastCapture.BP\_MillicastCapture\_C",NewName="/M2Core/Millicast/BP\_MillicastCapture.BP\_MillicastCapture\_C")

+PackageRedirects=(OldName="/ODK/Depreciated",NewName="/ODK/Deprecated",MatchSubstring=true) +ClassRedirects=(OldName="/ODK/Depreciated",NewName="/ODK/Deprecated",MatchSubstring=true) +ClassRedirects=(OldName="/M2Deprecated/LegacyContent/Core/BPC\_CharacterMoveSpeedComponent.BPC\_CharacterMoveSpeedComponent\_C",NewName="/ODK/Movement/BPC\_ODK\_MoveSpeedComponent.BPC\_ODK\_MoveSpeedComponent\_C")

</details>

#### Wallet View - BindToWalletViewTokenAdded delegate signature update

Date of change: 10/04/2026

Affected Features: BPMC\_ODK\_WalletComponent > WalletViews

What’s broken and why?

The signature for the OnTokenBalanceAdded Event Dispatcher has been updated to include an initial add boolean. This was necessary to inform the developer if the token is part of the wallet initialization, where currently owned tokens are added, or if it is a newly added token.

How to fix it?

Update any bindings to OnTokenBalanceAdded to include the already existing BP\_ODK\_Token and a new Initial Add boolean.

#### Wallet View - BindToWalletViewTokenBalanceUpdated delegate signature update

Date of change: 09/04/2026

Affected Features: BPMC\_ODK\_WalletComponent > WalletViews

What’s broken and why?

The signature for the OnTokenBalanceUpdated Event Dispatcher has been updated to include the previous balance. This was necessary to inform the developer if the balance update was positive or negative.

How to fix it?

Update any bindings to OnTokenBalanceUpdated to include the already existing BP\_ODK\_Token and a new PreviousBalance float.

#### LoadCharacterFromUrl no longer supports websocket URLs

Date of change: 27/03/2026

Affected Features: MML Avatars

What’s broken and why?

It was possible to load MML avatars from a websocket `wss://` URL with the `LoadCharacterFromUrl` node. This was undocumented, adds extra complexity and is unnecessary, as avatar models aren't dynamic. Other MML objects are unaffected and can use websockets as usual.

How to fix it?

All characters MMLs should be statically served from `https://` URLs instead.

#### Moving UI mode logic from the Player Controller onto the BPC\_ODK\_UIModeComponent

Date of change: 25/03/2026

Affected Features: UI mode

What’s broken and why?

We wanted to clean up the UI mode logic to make is simpler and have less of a footprint. We have move logic off the BP\_ODK\_PlayerControllerBase and moved it onto the BPC\_ODK\_UIModeComponent.

How to fix it?

The main thing that may be broken will be any bindings to the old OnUIModeChangeRequest event on BPC\_ODK\_UIModeComponent. It has now been renamed OnUIModeUpdated.\
\
You may have overriden some of the old functions on the BP\_ODK\_PlayerControllerBase regarding UI mode. If this is the case, you will need to create a derived class of BP\_ODK\_PlayerControllerBase and implement your logic inside the relevant functions. You can then configure your player controller to use your derived component class via the UIModeComponentClass property.

#### Movement Mode Id change from FName to DA\_ODK\_MovementModeId

Date of change: 24/03/2026

Affected Features: Movement

What’s broken and why?

We have decided to change the movement mode id type to make it more discoverable and reduce the chance of incorrect configuration through spelling mistakes with FNames. This includes the `HandleMovementModeUpdated` event on the `BPC_ODK_MovementModeComponentBase` component.

How to fix it?

If you interact with movement mode logic, simply use one of the the new movement mode id data assets instead of your existing FNames

#### DisconnectWebsocket calls now cancel all event listeners on that websocket

Date of change: 16/03/2026

Affected Features: M2\_WebPlatformRealtimeService

What’s broken and why?

Calling `M2_WebPlatformRealtimeService::DisconnectWebsocket` to explicitly disconnect a websocket will cancel any delegates added by calls to `ListenToResourceType`. This is to make the behaviour consistent with delegates added by `SendMessage` which were already being cancelled.

How to fix it?

If you call `DisconnectWebsocket` and then re-use it, you will need to set up any delegates again.

#### Removal of BPC\_PlayerEmoteVFXHandler

Date of change: 16/03/2026

Affected Features: Emotes

What’s broken and why?

The component is not needed now that we do not use JM\_PlayerEmotesComponent

How to fix it?

The component still exists in our deprecaited folder. Copy this into your project if you need it as it will be deleted at a later date.

#### No longer reading wallet emotes in emote component

Date of change: 16/03/2026

Affected Features: Emotes

What’s broken and why?

This functionality is no longer needed due to configuration of emotes in the web app.

How to fix it?

If you were using this functionality, you will need to manually grab emotes in the users wallet and then pipe that into the emote component using a custom source.

#### Removal of overlay and social buttons

Date of change: 13/03/2026

Affected Features: UI

What’s broken and why?

WBP\_OverlayButton and WBP\_SocialButton have been removed.

How to fix it?

Recreate similar assets

#### M2\_AnimInstance AnimVarsComponent member changed to AnimVarsProvider Interface

Date of change: 12/03/2026

Affected Features: AnimVars and Animation Blueprints

What’s broken and why?

Animation blueprints based off M2\_AnimInstance that were reading the AnimVarsComponent will no longer compile.

This change is done to abstract the UObjects containing the AnimVars via interfaces so that we aren't restricted to drive from `MorpheusAnimVarsComponent` and can use the native `ActorComponent` to provide AnimVars to the Animation Blueprint. This is also done to facilitate the integration of Mass Entity into Morpheus and Animated Crowd.

How to fix it?

Instead of getting the AnimVarsComponent, get the AnimVarsProvider interface and cast to an AnimVarsComponent

How to test it?

The Animation blueprint is working as expected

#### Legacy camera classes replaced with ODK versions

Date of change: 09/03/2026

Affected Features: Camera control

What’s broken and why?

As part of removing dependencies on the legacy `M2Deprecated` plugin, some camera-related classes have been moved into the ODK, and project may need to update references (the existing classes still exist, so redirectors haven't been added).

How to fix it?

Replace these references:

* `BP_PlayerCameraManagerBase` -> `BP_ODK_PlayerCameraManagerBase`
* `BPC_M2_CameraControlComponent` -> `BPC_ODK_CameraControlComponent`
* `BPC_M2_CameraControlInterface` -> `BPC_ODK_CameraControlInterface`

#### OverShoulderCam removed from ODK

Date of change: 03/03/2026

Affected Features: Camera controls

What’s broken and why?

The over-the-shoulder camera mode option has been removed from `WBP_Settings_Controls_Game` and related assets, because it's currently unused and relies heavily on many assets in the removed M2Deprecated plugin.

How to fix it?

If you're currently using the ODK's over-the-shoulder implementation, copy the required assets from the M2Deprecated plugin directly into your own project.

#### Legacy BP\_Y\_Jumpad\_Base triggers may have moved

Date of change: 24/02/2026

Affected Features: Launch pads

What’s broken and why?

The deprecated `BP_Y_Jumpad_Base` blueprint has been reparented to remove dependencies on the M2Deprecated plugin. It's possible that this may affect the transform on the Trigger component of any child classes in your project.

How to fix it?

If the jump pads aren't functioning, check the transform of the trigger and volume and adjust as required.

#### HUD class simplification - removal of deprecated components

Date of change: 24/02/2026

Affected Features: HUD

What’s broken and why?

`BP_ODK_HUD` used to derive from the deprecated `BP_Origin_HUD` which contained several deprecated components. It now derives from a simpler base class with `BPC_TutorialPromptsManager`, `BPC_GlobalMessageReceiver`, `J_EmoteWidgetManager` and `J_WidgetVisibilityChecker` removed, as they're likely not used in any projects.

One side effect is that any legacy tutorial widgets present in your HUD (e.g. `WBP_TutorialPrompts`) may not behave correctly.

How to fix it?

These components can be re-added in your own derived HUD class if required. Legacy tutorial widgets may need removing entirely if they're appearing incorrectly. Any calls to parent functions on the old base class may cause compile errors, which can be fixed by re-adding the correct call to the parent function.

#### Image Cache World Service GetImageForUrl callback signature change

Date of change: 24/02/2026

Affected Features: Image Cache

What’s broken and why?

`GetImageForUrl` now takes a context object that is provided as a parameter in it's deleged.

How to fix it?

Any usage of `GetImageForUrl` will need an additional object param adding to it.\
\
Persistence World Service Callbacks signature changes.

#### Persistence World Service Callbacks signature changes

Date of change: 03/02/2026

Affected Features: Persistence

What’s broken and why?

1. For more usability, we added the key corresponding to the grabbed value to the `BP_ODK_PersistenceWorldService` read callbacks as a parameter.
2. Renamed `OnValueLoaded` to `OnValueRead` to better indicate that the value can be queried and read multiple times

How to fix it?

1. Add a string parameter to you callback functions.\
   ![](/files/tpUv8E2d6rRQUijKB2BK) -> ![](/files/H5BJsnNyUVnSkNnhtd98)
2. Reattach your callbacks to the `OnValueRead` event pin

#### Removal of functions on BP\_ODK\_PlayerCharacterBase

Date of change: 21/01/2026

Affected Features: ODK Render Target

What’s broken and why?

Some logic on the `BP_ODK_PlayerCharacterBase` needed to be moved for better accessibility and logic clarity.\
\
Functions removed:\
`GetScaledInitialGravityScale`\
`GetMovementOrientation`\
`SetZoomEnabled`

How to fix it?

`GetScaledInitialGravityScale` has been completely removed. You can use GravityScale off the character movement component.

To fix `GetMovementOrientation` removal, you should use `GetControlRotation`

To fix `SetZoomEnabled` removal, you should use `AddToZoomDisableStack` and `RemoveFromZoomDisableStack` on `BPC_ODK_CameraControlComponent`.

#### Removal of Game Settings Logic on ODK Player Render Target

Date of change: 12/01/2026

Affected Features: Game Settings

What’s broken and why?

Functions `GetControllerSensitivity`, `HandleCustomSettingChanged` and `InitializeGameSettings` have been removed from `BP_ODK_PlayerCharacterBase`. Game setting logic will now be performed by `BP_ODK_GameSettingsWorldService`.

How to fix it?

Use the `BP_ODK_GameSettingsWorldService` for accessing and updating game settings.

#### Persistence API update to allow for wider KV store access

Date of change: 08/01/2026

Affected Features: Persistence

What’s broken and why?

Nothing will be broken but you may have issues in the future regarding persistence if this is not rectified.

How to fix it?

You will need to switch your "KV Store Service Class" in your world settings to `BP_ODK_KVStoreService` .

#### Koda Cam and Photo Album Refactor

Date of change: 16/12/2025

Affected Features: Koda Cam

What’s broken and why?

Koda cam photos, once taken, will be automatically uploaded to remote storage. Unreal will no longer start the minting process for koda cam photos. Instead, the overlay will be used to view and mint photos. `WBP_ODK_PhotoAlbum` will be deprecated soon. The API on `BPC_ODK_ScreenshotComponent` has been updated to reflect these changes. Some functions have been removed although it is unlikely your project uses them. Currently there is no need for Unreal projects to grab remotely stored photo data. We do plan to support this soon if your project needs this.

How to fix it?

You will no longer be able to mint screenshots or display stored screenshots in Unreal.

#### Removal of the Progression System

Date of change: 15/12/2025

Affected Features: Progression System

What’s broken and why?

We have removed all assets concerning the granting of XP. This is due to a wish to only allow XP granting to certain events. We will most likely be adding this feature back in future when we can more easily limit it's usage.\
\
Removed classes:\
`BP_ODK_ProgressionWorldService`\
`BP_ODK_XPTask`

How to fix it?

You will not be able to grant XP anymore so simply patching up your assets so they compile will suffice.

#### Emote Filtering and Sorting Update

Date of change: 05/11/2025

Affected Features: Emotes

What’s broken and why?

We wanted to provide a better API for sorting emotes on the emote wheel and for configuring what emotes are available in certain game states. Functions concerning overriding the emote selection on `BPMC_ODK_EmotesComponent` have been updated and renamed to give more clarity and functionality. The function is now called `PushCustomEmoteSource`. This function now has an additional parameter to configure how emotes are sorted whilst this custom source is active.\
\
We also wanted to take the change to remove functionality that is most likely no longer relevant. This includes removal of the function `GetFilteredEmotes` from the `BPMC_ODK_EmotesComponent`. This function will likely never be used by downstream developers.

How to fix it?

If you were using `GetFilteredEmotes`, you should be able to create your own version of the function filtering on `GetAllPlayableEmotes` on `BPMC_ODK_EmotesComponent`.

#### Removed PlayGlitchVFX from BP\_ODK\_PlayerCharacterBase

Date of change: 15/10/2025

Affected Features: Glitch Visual Effect

What’s broken and why?

We want to reduce logic footprint of our base render target. Playing a specific visual effect in a function on the base character isn't ideal as most project will not be playing this visual effect.

How to fix it?

If you were calling `PlayGlitchVFX`, a component has been added to our base render target that allows for easy playing of niagara effects: `BPC_ODK_NiagaraEffectsManagerComponent`. This is an example of how it can be used to show the "Glitch" effect.

<figure><img src="/files/HUlHitWrkoFWPI5p1kR5" alt=""><figcaption></figcaption></figure>

####

#### Initialize Web Browser API Update

Date of change: 02/10/2025

Affected Features: Web Browser

What’s broken and why?

On order to facilitate earlier loading of the web browser, we have modified the Initialize function on the `BP_ODK_WebBrowserWorldService` to take a morpheus actor parameter.

How to fix it?

Pass in the player morpheus actor to this function if you are calling this explicitly in your own project blueprint logic.

#### Persistence API Update

Date of change: 02/10/2025

Affected Features: Persistence / Key Value Store

What’s broken and why?

We wanted to streamline the ODK persistence world service API. RegisterInterest functions will now perform a "read" event and then also "subscribe" to further updates. The removes the need for developers to call both "read" and "subscribe" methods separately.

Additionally, we wanted to remove some of its dependencies on the MSquared web platform layer. As such, we have deprecated `BP_ODK_WPProfileDataProvider`. It can be removed from you map settings and replaced with `BP_ODK_ProfileDataProvider`.

How to fix it?

The API has slightly changed on our RegisterInterest functions on the ODK persistence world service. You will simply need to add a bool to the arguments of your callback delegates.

#### HandleBootflowStarted removed from BP\_ODK\_PlayerCharacterBase

Date of change: 07/10/2025

Affected Features: Player Character Render Target

What’s broken and why?

This function is obsolete as the bootflow will have started when `BP_ODK_PlayerCharacterBase` is created. Just use `BeginPlay`.

How to fix it?

If you have overridden this function, move your logic into begin play.

#### Roles Component refactor

Date of change: 26/09/2025

Affected Features: Roles, Resizing

What’s broken and why?

The current Role Component (JM Roles Component) is being deprecated and we have to replace it with a new, more streamlined version. As a result, the Resizing Component has also been reworked and simplified. The new Roles Component will also be less rigid than before, allowing users to attach any data they want to their roles in the form of a M2 Data Asset.

How to fix it?

In each of your levels, open the World Settings and search for 'Role'. There is one new section - Morpheus Platform > Roles > Role List Provider. Make sure this is set to `BP_M2_RoleDataProvider`. This is also where you will supply your role data table, once created. There is another Roles section in the World Settings - Morpheus Platform (Deprecated) -> Roles. This section is no longer used, and can be cleared, as seen below:

<figure><img src="/files/h3sjD9b6YlTaLqOpQ96r" alt=""><figcaption></figcaption></figure>

You will need to create a data table using the `S_ODK_RoleProperties` structure. This will hold:

1. Role Name (required)
2. Granted Capabilities (required)
3. Pawn LOD level data (optional)
4. Additional Properties - M2 Data Asset containing any addition data you wish to attach to the role (optional)

<figure><img src="/files/PDsp7y4Wy37RTPBPKRrb" alt=""><figcaption></figcaption></figure>

You can convert your old roles tables over, but remember that if you have rows that are based on others in your old table, you will need to move all the data from the base role and the inheriting role over into the new singular row. The only base capability we recommend adding to all players is `Capabilities.VoiceChat.Enabled` to allow users to have access to voice chat.

Once you've created your roles table, you can set that roles table as the default roles table for all levels across your project, by adding a reference to the DT within your `M2DefaultContent.ini`:

```
[/Script/M2Content.M2_WorldRolesComponent]
RolesDataTable=<DT_PATH>
```

If you were using any of the other properties on the old structure, for example `Associated Colour`, you would need to include this in the M2 Data Asset you create to attach to the role and handle the use of this as appropriate (an example later using a float can be found later in this document). The names of these rows will be your role names. We recommend using the settings menu to allow players to switch between available roles, however you can initiate the change by calling `BPMC_ODK_RolesComponent::RequestRoleChange` .

<figure><img src="/files/4XFkuIVgmZUGJLCysm6h" alt=""><figcaption></figcaption></figure>

An example of using the Roles Component to swap a users role, in this case to 'Player'.

Any Morpheus Actor that was based on `BPM_ODK_PlayerCharacter` will now have the new Roles and Resize component. Any calls to change role on these will need to be swapped to calling Request Role Change on the Roles Component. Pass in the role name you want, and the component will check to see if the role is valid for this user and swap them over, handling the Capabilities changes and Pawn Override for you. On Role Updated will be fired on the Morpheus Actor, returning the new role/data and the previous role/data. Here you can pull any data you need from your M2 Data Asset for the role. We have an example use of this in `BPM_ODK_ExampleCombatCharacter`, see below.

Resizing is now simplified - you now set the relative size you want by calling Set Scale on the Resizing Component. Setting it to 1.0 will return them to normal size. Note that if you have multiple things that can affect the player size, you will need to track these yourself.

<figure><img src="/files/OD4S3AU02HcjkCE6YSLB" alt=""><figcaption></figcaption></figure>

Above is an example of the new flow on role change, pulling your own data from the role properties, and using the Resize component.

#### Input Action Binding Delegate Update

Date of change: 11/09/2025

Affected Features: Input

What’s broken and why?

We have updated the parameters available on callbacks when binding to input actions through the `BPC_ODK_InputComponent`. The concerned input action is now passed in as a parameter.

How to fix it?

Simply add an input action param to your existing callbacks.

**Dynamic Interaction Component**

Date of change: 08/09/2025

Affected Features: Interaction Component

What’s broken and why?

The `BPC_ODK_InteractionComponent` that was attached to the `BP_ODK_PlayerCharacterBase` has been removed and is now added at runtime. This allows the component to be overridden by downstream projects.

How to fix it?

If you were using a direct reference to this component, simply use the `GetComponentByClass` function to retrieve the component.

**Update to Launch API**

Date of change: 08/09/2025

Affected Features: Launch Movement Mode

What’s broken and why?

We have updated the launch interface and some surrounding functions to group launch parameters into a struct.

How to fix it?

Where your logic has broken, make a new `S_ODK_LaunchParams` struct and hook up the `HasCustomAirControl` and `CustomAirControl` values you were using previously.

\
**Obtaining a Player's Primary Wallet Address**

Date of change: 04/09/2025

Affected Features: Wallet

What’s broken and why?

A couple of adjustment have been done to the wallet initialization flow. These changes should not impact downstream projects. As part of these changes, a small bit of cleaning was added to `BPMC_ODK_WalletComponent`. We now have accessors for the primary wallet and all linked wallets for the player. Please not that both of these accessors are also available on the `BPMC_ODK_PlayerProfileComponent`.

How to fix it?

Instead of using the old property `WalletAddress`, please use the accessor `GetPrimaryWallet`.

**Avatar Logic Centralization**

Date of change: 03/09/2025

Affected Features: Avatars/Yuga Characters

What’s broken and why?

We have centralised logic that was previously spread across the `BP_ODK_PlayerCharacterBase` and `BPM_ODK_PlayerCharacterBase` onto a new component `BPC_ODK_AvatarComponent` component that gets dynamically created on `BP_ODK_PlayerCharacterBase`. This component can be overridden by setting the `AvatarComponentClass` property on `BP_ODK_PlayerCharacterBase`. The `BPC_ODK_AvatarComponent` is where the bulk of avatar functionality will now be performed. `BPMC_AvatarInfo` has been renamed `BPMC_ODK_AvatarComponent` and is now used purely to replicate the `AvatarType` property.\
\
This setup is required as morpheus components can not be added dynamically to actors. Deriving and overriding component logic can only be done using standard actor component.

How to fix it?

Any modifications to the logic that used to exist on the `BP_ODK_PlayerCharacterBase` or `BPM_ODK_PlayerCharacterBase` will need to be moved. We would recommend deriving a new component from `BPC_ODK_AvatarComponent` and overriding `AvatarComponentClass` on `BP_ODK_PlayerCharacterBase` to use the new component class.

**Moving Away from MSquared Player Profiles**

Date of change: 03/09/2025

Affected Features: Player Profiles

What’s broken and why?

The ODK is divorcing itself from MSquared player profile systems. `BPMC_ODK_ProfileOverlayHandling` has been renamed `BPMC_ODK_PlayerProfileComponent`. It can be used to access: player name, avatar token id, profile picture and linked wallets.

How to fix it?

The `BPMC_ODK_PlayerProfileComponent` has delegates and accessor functions similar to the old MSquared player profile component so should be a relatively painless switch over.

**New ODK Singleton Spawning**

Date of change: 02/09/2025

Affected Features: World Singletons

What’s broken and why?

The `BP_ODK_BootflowSingleton` is now responsible for spawning all base ODK morpheus and non-morpheus singletons. This allows the ODK to add and remove singletons without the need for downstream projects to update their world settings.

How to fix it?\
\
Add `BP_ODK_BootflowSingleton` to "Additional Singletons" in world settings if it is not there already.

The following should be removed from "Additional Singletons":\
\- `BPM_ObserverHUDControls`\
\- `BPM_ObserverCameraControls`\
\
The following should be removed from "Non Morpheus Singletons":\
\- `BP_glTFMaterialIndex`\
\- `BP_MillicastCapture`\
\- `BP_ODKCarnivalMaterialStore`

**Attachments Refactor**

Date of change: 26/08/2025

Affected Features: Attachments

What’s broken and why?

We wanted to simplify how developers configure attachments so have moved configuration off the singleton defined on the world settings and onto the `BPMC_ODK_AttachmentsComponent`.

How to fix it?

Data tables are now where you can configure attachments. These data tables can be added to the `AttachmentsDataTables` property on `BPMC_ODK_AttachmentsComponent`. `DT_ODK_KodaCamAttachments` is an example of how to setup the table.\
\
The `BP_AttachmentManagerODK` has been removed. You will need to remove it from your world settings.\
`PDA_AttachmentList` has been moved to depreciated. Please delete any derived PDAs from your projects.\
\
`BPMC_ODK_AttachmentsComponent` now takes attachment ids as Names rather than strings.

#### Movement Props Cleanup

Date of change: 22/08/2025

Affected Features: Movement props

What’s broken and why?

Blueprint props that allow players to enter new movement modes were quite untidy. We have taken the oppertunity to make each props purpose more clear and tidy the logic. This may impact movement props in your project levels. You may need to switch to using a different blueprint prop that is more appropriate for your use case. We now offer these props in the ODK:\
\
`BP_ODK_Launcher` - Launches the player into the air on a path to a destination.\
`BP_ODK_GrindingRail` - Allow you to define a curve for the player to grind along.\
`BP_ODK_BouncePad` - Will launch the player into the air but will use the "bounce" movement mode. The player will be put into a tumbling animation.\
`BP_ODK_AirLift` - Will put the player into the "air lift" movement mode.\
\
One note here, the blueprint interefaces `BPI_ODK_Bounce` and `BPI_ODK_Launch` have been implemented on `BPC_ODK_MovementComponent` and removed from `BP_ODK_PlayerCharacterBase`.


# v9.4

#### Morpheus Array Type Redefinition

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?**

* 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.


# v9.3

### Legacy Skin Dependency

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`

```
[/Script/JunoCoreUI.J_UISkinsSettings]
bEnableLegacySkinningSystem=True
```

### Domain Check

Date 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.

<figure><img src="/files/QBDOqR5BadI66DiOYHJi" alt=""><figcaption></figcaption></figure>

### Emotes Refactor

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`.

### Updated allowed avatar collection list

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.

### Token ID management rework

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:

```
"TokenIDLiveConfigName": {
	"type": "array",
	"items": {
		"type": "number"
	},
	"default": [1, 8],
	"description": "On apechain, the token is 1. On curtis, it is 8."
}
```

### Project Live Config Location Update

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.

<figure><img src="/files/KsHPNAmt61JZ97VnHCRK" alt=""><figcaption></figcaption></figure>

###

### New Bootflow Singleton

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.

1. 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`
2. 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.

<figure><img src="/files/SYCCxtKFy5NwcRMKQhiU" alt=""><figcaption></figcaption></figure>

How to test it?

Ensure your bootflow starts correctly, and you're able to apply your set avatar model.

###

### Movement Mode Refactor

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.

###

### Improved Mixpanel Analytics to support other data types

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:

![](/files/a1VuD13QWGVxR3StQrMV)

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.

###

### Removal of BPFL\_ODK\_WidgetHandler.

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.


# v8.3

### **Live Config Changes**

**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*

**How to fix it?**

*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.

{% file src="/files/wuIpglU3rJdJZRFPMrg0" %}

### **Editor Sign-in 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.*


# v8.2

### Updates to ODK's project schema

**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?**

*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.

{% file src="/files/HivPOtlUPrw2Q8uqAOaa" %}

### Connect Button on the Dashboard redirects to <https://o7e.dev/> instead of the GFN Stream.

**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*

### Bubbles Content Moved

**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.*

### ODK Settings Menu

**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_ODKSettings`widget that leverages the* [*ODK Base UI*](https://docs.otherside.xyz/odk-documentation/documentation/odk-plugin/odk-widget/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.

### Inspector Admin Capability Update

**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.*

### Retirement of Avatar Selector

**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.*

####

####

### Crowd Audio Component Change

**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.*

####

####

### Attachments Component

**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`.*

### UI Mode Component

**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.

### **Update to Profile Data Provider class**

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?

1. For any prior levels, you need to manually update the "Profile Data Provider Class" to `BP_ODK_WPProfileDataProvider`<br>

   <figure><img src="/files/jHkzcPR1jB1e9fhFpR8D" alt=""><figcaption></figcaption></figure>

How to test it?

Log in and validate that your characters are retrieving their player names and other profile information correctly.

### **Moved default emote definition to BPC\_ODK\_EmotesComponent**

**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*<br>

<figure><img src="/files/1edlOKReU0q6p2JIYmRX" alt=""><figcaption></figcaption></figure>

**How to test it?**

*Play in editor and bring up your emote wheel. The defined emotes should appear on the wheel.*

### **Koda Lod Level replacement with BPDA\_LODLevelsByAvatar**

**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?**

1. *Create a new DA from `BPDA_LODLevelsByAvatar`*
2. *Set your existing `BPDA_LODLevels` properties per avatar*
3. *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*<br>

### **Editor Sign-in Changes**

**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*](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.*

### **Jump Component**

**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.*

### **Text Chat Update**

**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.**\ <br>

### **Persistence Update**

**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.*

### **Removal of Vending Machines**

**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.*

### **Asset removals**

**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.

### **Interaction Refactor**

**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.*

### **Selfie Camera Refactor**

**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.

### World Services update

**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.*

### Reparenting of Base Classes

**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.*


# v6.0

### Configuring Player Character Input Mapping Contexts

**Date of change:** 11/12/2024

**Affected Features:** Input

**What’s broken and why?**

*The way we configure the default input mapping context on a character is changing to allow us to add IMCs to the base ODK player character.*

**How to fix it?**

*You should override GetDefaultInputMappingContexts in the following way. Create a new local variable in your function and configure it to have the extra input mapping contexts you need.*

<figure><img src="/files/lZrlGg4AyhwrhLd7s3LB" alt=""><figcaption></figcaption></figure>

**How to test it?**

*Ensure your input mappings are being used in PIE.*

### BPM\_ODK\_PlayerCharacterBase > Server\_TransferObjects

**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.*\
\
![](/files/vkOogtnYDP9AF456Ub7K)

**How to test it?**

*After replacing the deprecated events in your blueprints, you will receive a callback with a success or faulure message.*


# Characters

Let's talk about the characters who you'll run into on Otherside

<figure><img src="/files/IsU1BlYugO3XubmQwLFT" alt="There are only 10,000 Kodas."><figcaption><p>There are only 10,000 Kodas</p></figcaption></figure>

## Kodas

Kodas are the main characters of Otherside.\
They use **Soma** to create forms and **Chaos** to take them away.\
If someone owns a Koda, they can play as that Koda.

In the Resource game, Kodas have special uses that gives players advantages.

## Voyagers

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.

## Custom Characters

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**.


# Creating a Custom Character

In this documentation we've set up a few sections to go over:

[Key Concepts for 3D Characters](/odk-docs/characters/creating-a-custom-character/key-concepts-for-3d-characters) - Quick overview of some common 3D terms and approaches

[Technical Specifications](/odk-docs/characters/creating-a-custom-character/technical-specifications) - Basic 3D asset specifications

[Asset Production Best Practices](/odk-docs/characters/creating-a-custom-character/asset-production-best-practices) - General Guides for how to create good character art for Otherside

[Creating A Simple Avatar Collection](/odk-docs/characters/creating-a-custom-character/creating-a-simple-avatar-collection) - A full tutorial of creating a complete avatar collection.

[Making Avatars Available in the Otherside](/odk-docs/characters/making-avatars-available-in-the-otherside) - 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 → [Avatars | Morpheus Docs](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/features-and-tutorials/avatars) . 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.

### 🦴 Base Skeleton for Otherside

{% file src="/files/YqKIVYmVLdPXOsG7FaHV" %}


# Key Concepts for 3D Characters

## **Key Concepts for 3D Characters**

Before you make a character, it helps to know a few basics. Don’t worry—you don’t need to be an expert! These ideas will just give you the vocabulary to follow the steps later.

***

### **Vertices (The Dots)**

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.

***

### **Mesh (The Skin)**

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**.

***

### **Skeleton (The Bones)**

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 (Skinning)**

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.

***

### **Textures & Materials (The Paint and Clothing)**

* 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 / GLB (The Package)**

* 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 (The Description)**

* MML stands for Metaversal Markup Language. It's like an HTML file but instead of a website it is a 3D object or scene

## **Tips for Success**

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.

***

### **1. Character Proportions**

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.

***

### **2. Avoid Transparency**

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.

***

### **3. Work End-to-End and Iterate**

Building an avatar has many steps, and each character can present unique challenges.

A great strategy is to:

1. Start simple (a rough block out).
2. Get it running in Otherside as soon as possible.
3. 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.

### **1. Rough Block Out**

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.

***

### **2. Material Testing**

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.

***

### **3. Asset Planning**

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.

***

### **4. Pipeline Planning**

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.

***

### **5. Asset Production**

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**.

***

### **6. QA (Quality Assurance)**

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.


# Technical Specifications

Below are the technical requirements and supported features for avatars in Otherside. Following these ensures your assets import cleanly and display correctly.

***

### **File Requirements**

#### 3D Asset

* **3D Model Format:** `GLB` (binary glTF)
* **Texture Formats:** `JPG` or `PNG`
* **Shading Model:** `PBR` (Physically Based Rendering)
* **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`

#### MML File

* UTF-8 encoding

***

### **Supported Texture Channels**

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.

| **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)** | <p>A single texture that combines three grayscale maps:<br><strong>Occlusion</strong> – Adds baked-in shadows and depth.<br><strong>Roughness</strong> – Dark = shiny surface, Bright = rough surface.<br><strong>Metalness</strong> – Dark = non-metal (plastic, paint), Bright = metallic. Packing these maps into one texture reduces file size and improves performance.</p> | Linear         | `ORM`     |
| **Emissive**     | Defines areas of the model that emit light (e.g., glowing eyes, neon strips). Use sparingly, only when necessary.                                                                                                                                                                                                                                                                | sRGB           | `E`       |


# Creating A Simple Avatar Collection

In this guide we will walk through the steps of creating an incredibly simple avatar collection...Boxie!

There are many tools for and techniques for creating 3D avatars, this guide will walk through some of the most straight forward approaches.

### Prerequisites

* Blender 4.5 LTS -[ https://www.blender.org/download/lts/4-5/](https://www.blender.org/download/lts/4-5/)
* MML Avatar Tool - <https://github.com/mml-io/avatar-tools> (only if completing the pipeline section)
  * node.js v20.x - <https://nodejs.org/dist/v20.11.1/node-v20.11.1-win-x64.zip> / <https://nodejs.org/dist/v20.11.1/node-v20.11.1-darwin-x64.tar.gz>
* ODK Unreal Editor (optional for testing)

### Resources <a href="#resources" id="resources"></a>

{% file src="/files/YqKIVYmVLdPXOsG7FaHV" %}

{% file src="/files/spAIOREs9MOcBmLs3NdP" %}

{% file src="/files/ECp4duvC0rm0jjh3D40A" %}

<figure><img src="/files/ugFQyEIcw7CrsiNDxtSw" alt=""><figcaption></figcaption></figure>


# Setting Up Blender

If you haven't already downloaded and installed blender go ahead and do that now. <https://www.blender.org/download/lts/4-5/>

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.

<figure><img src="/files/kz64n6a24OKJy4AFRY84" alt=""><figcaption></figcaption></figure>


# Setting Up The Boxie Collection

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 [Creating A Simple Avatar Collection](/odk-docs/characters/creating-a-custom-character/creating-a-simple-avatar-collection#resources).

The Boxie collection only has three different traits categories:

* Head
  * classic box
  * sphere
  * 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.

```python
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)

```

You can run this script directly in Blender by:

* Changing the view to the scripting tab

<figure><img src="/files/5rzJfkdCqrhq1RFoyhJA" alt=""><figcaption></figcaption></figure>

* Create a new text block by clicking the `New Button`

<figure><img src="/files/RTsdYBusmEtl6nbjv2mI" alt=""><figcaption></figcaption></figure>

* 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.

<figure><img src="/files/5JFr5Q7KZr5wxVPYeTOx" alt=""><figcaption></figcaption></figure>

This should save out a JSON file that is structured like an NFT collection metadata.

{% file src="/files/ECp4duvC0rm0jjh3D40A" %}


# Block Out Character

## Setup Reference

<figure><img src="/files/ugFQyEIcw7CrsiNDxtSw" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/jVtlOdSFJDvFjlAtsQjg" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/DgA3KIEwZSy9eEonfIAJ" alt=""><figcaption></figcaption></figure>

If the sidebar on the right isn't already visible click on the small right arrow `<` to open it up.

<figure><img src="/files/ThBWuyRjn0p7OO18WPAu" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/RvAep9G5MkYWZoekwpLc" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/HVQvNwPSjyd5hyRhfFv5" alt=""><figcaption></figcaption></figure>

With the plane selected press the `New` Button on top of the `Shader Editor` panel.

<figure><img src="/files/hk6BkkIIqnuJs5mXFgii" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/E13qvZ4aVD8Y20NQiEnx" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/sOHoLBtTtfBJGox6s9rW" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/bkSzuV1V1w7wSmEFzKkJ" alt=""><figcaption></figcaption></figure>

At the bottom there is a section called `Color Management`. Set this to `Standard`

<figure><img src="/files/WnOhiZAyTDL8UNJzlqGP" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/Tg1Xw5PhA4Mns0VNbOvx" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/4fCH1Ev8KpIex9gQpNml" alt=""><figcaption></figcaption></figure>

## Blocking Out

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 -> <https://docs.blender.org/manual/en/4.5/scene_layout/object/editing/transform/introduction.html>

<figure><img src="/files/N9x2I40oy9sO02xiJCHg" alt=""><figcaption></figcaption></figure>

You can add cubes just like we added the Plane above.

<figure><img src="/files/NNeEbPZ8UFlPwiQ5rmWs" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/WbMjvpzaJArT4ApXo27Y" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/dGyO18tOjxM6qrli0hXB" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/undsGAq1fLkyv95584Lt" alt=""><figcaption></figcaption></figure>

If you feel confident you are done with the reference image, you can delete that now.

## Weighting

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 [Creating A Simple Avatar Collection](/odk-docs/characters/creating-a-custom-character/creating-a-simple-avatar-collection#resources) and import it into Blender.

<figure><img src="/files/B7CWEoacqR9oVTKsDgmP" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ncufLEwlYRZEOOqVRUlt" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/vGmPluhhteBBBE3GejlA" alt=""><figcaption></figcaption></figure>

I am also going to to rename the armature for organization.

<figure><img src="/files/6XoRpl6tR0O8HWAEyMoc" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/eQ04KGoyUU30P6ORJlTa" alt=""><figcaption></figcaption></figure>

### Armature Editing Tips

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`

<figure><img src="/files/HCO3td9tRhua5tmnaSSI" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
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.
{% endhint %}

<figure><img src="/files/uoJGbHTrWOwvSBS8q8F1" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/VRrvIcD3aMdyBoXIFsoj" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/dSDM7PnpCJTGjKw1UjSP" alt=""><figcaption></figcaption></figure>

With the cursor over the 3D Viewport press `Ctrl + P` to open the parenting menu and select `With Empty Groups`

<figure><img src="/files/TvYmbKLaK4HLbEIAiT77" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/4eWqKBDNs4JCaH6fmX0i" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/IQnmHugAdwBNetBSzPjm" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/4t5GzmLmWRyhlLy27mC9" alt=""><figcaption></figcaption></figure>

### Export and Test

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.

<figure><img src="/files/l0CaWAW1cn7xerZpygKM" alt=""><figcaption></figcaption></figure>

Make sure the format is `glTF Binaryt (.glb)` and under `Include` `Visible Objects` is checked.

<figure><img src="/files/ZfNBS6MJ7MzU8k5mDuCG" alt=""><figcaption></figcaption></figure>

To do a quick test of the avatar open up <https://mml-io.github.io/avatar-tools/main/tools/gltf-avatar-exporter/> 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.

<figure><img src="/files/Pav2FpYlQQeOi4U98eqv" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/CsFAEQInIBo0UQAlaThY" alt=""><figcaption></figcaption></figure>


# Further Testing

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
  * Apply animations

Let's start by creating a new project. Start the ODK Launcher and make sure you are on the `Templates` Tab.

<figure><img src="/files/sjhgHELEnBxleHWMB5wt" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/Zrh1PPaLE1iV2HGNra3Z" alt=""><figcaption></figcaption></figure>

Create a new folder called `Blueprints` at the root of the content folder.

<figure><img src="/files/NWZt4Hsnj8oBUCoocTBk" alt=""><figcaption></figcaption></figure>

Right click in the empty window to and click `Blueprint Class`.

<figure><img src="/files/eWvAuEFj99f5nomrrgkX" alt=""><figcaption></figcaption></figure>

Select `Actor`

<figure><img src="/files/f34pp7Fh2mFf6mWezzOI" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/vtf9y4s1az183RkhtZhR" alt=""><figcaption></figcaption></figure>

Press the `+` next to Variables to add a new variable called `Animations`

<figure><img src="/files/ax1eiceHW5iuzpwYng9C" alt=""><figcaption></figcaption></figure>

Change the type to `Animation Asset`

<figure><img src="/files/zpJXyAceoaANUc6CbzrE" alt=""><figcaption></figcaption></figure>

Make the variable public by toggling the eye next to it.

<figure><img src="/files/OF8W2ZWD8DAU5MACM0m6" alt=""><figcaption></figcaption></figure>

With the variable still selected in the details panel on the right update the variable type to be `Array`.

<figure><img src="/files/2cg20z6D1bH4hEoLz4At" alt=""><figcaption></figcaption></figure>

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:

<figure><img src="/files/nNbHbZ6KGZwgAIyKzxqd" alt=""><figcaption></figcaption></figure>

In the EventGraph press `tab` to add a node `glTF Load Asset from Filename`

<figure><img src="/files/ypq3l3FTcIeRSYXJv13z" alt=""><figcaption></figcaption></figure>

Drag and drop `GLB path` from the Variables panel into the Event Graph and select `Get GLB Path`

<figure><img src="/files/5HiHCVjj9P47LcQZMA3o" alt=""><figcaption></figcaption></figure>

Right Click on the GLB Path pin and click `Split Struct Pin`

<figure><img src="/files/V7oEW9koZgwXDrlrsKBI" alt=""><figcaption></figcaption></figure>

Now you can connect the pin on GLB Path to the file name on the Load Asset from Filename node.

<figure><img src="/files/odXrxiPgNOvHc34en7eS" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/wV1kbBAtdzwSZ35xLRNC" alt=""><figcaption></figcaption></figure>

Connect the pins as shown in the images below to the Load Asset node.

<figure><img src="/files/oHM9ZgF6zGHwJyRBbrGT" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/lLw0qbyeKMU95pT9VErW" alt=""><figcaption></figcaption></figure>

In the dropdown for `Transform Base Type` choose `YForward`

<figure><img src="/files/MggVsVkjvNHEcOxQRO2u" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/yW5XQYNxr6NbPVjU0Ale" alt=""><figcaption></figcaption></figure>

From the `Skeleton` dropdown select `SKEL_UE5Mannequin`

<figure><img src="/files/9Doei3yl9lSzXw3yDYwC" alt=""><figcaption></figcaption></figure>

From the Variables panel drag `Animations` out to the Event Graph and select `Get Animations`.

<figure><img src="/files/XaqKETopSqwC6zxJuUjU" alt=""><figcaption></figcaption></figure>

From the Pin on `Animations` pull off a connection and then select `For Each Loop`.

<figure><img src="/files/fwXTPinDPS8p9UhimH0v" alt=""><figcaption></figcaption></figure>

Connect the Exec pin from load skeletal mesh to the For Loop.

<figure><img src="/files/YBUyVfOBAMe0rp8hDpz2" alt=""><figcaption></figcaption></figure>

Add an `Add Skeletal Mesh Component` Node

<figure><img src="/files/lpNsN67bJR242UPQa6WJ" alt=""><figcaption></figcaption></figure>

Pull a connection off of the `Relative Transform` Pin and select `Make Transform`

<figure><img src="/files/Uw0S9LxHYKDmyfxmtTm1" alt=""><figcaption></figcaption></figure>

Split the `Location` Struct Pin

<figure><img src="/files/GEd7Utt5eHShL9dhOgiN" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/RxBngKwReiikwi6TKR0P" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/PEbbKvnyaF1f4dWE9PiK" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ZmnYRBFihL9U95WcMtu9" alt=""><figcaption></figcaption></figure>

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`

<figure><img src="/files/UZYIGxKRQSnXOeSPRNVm" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/k902KEDxmOqXdqCblVZP" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/p7VzGqFfm4ZlnDrnjZuf" alt=""><figcaption></figcaption></figure>

Back in our main level editor window, drag and drop the `BP_AvatarPreview` into the level.

<figure><img src="/files/Nf8zFLviM2UahJfrZLgU" alt=""><figcaption></figcaption></figure>

In the details panel in the bottom right of the screen we can an animation by pressing the `+` Next to the `Animations` label.

<figure><img src="/files/gwiNSBUSE6OrhAogeX4p" alt=""><figcaption></figcaption></figure>

For our first test animation I am just going to add a Range of Motion or ROM animation sequence.

<figure><img src="/files/i7NddOQ5fpwLaRmajB0H" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/LwcilWWCu6LAqHu8MobG" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Gjg0W9fjiQoQ2hm8u3AJ" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/Ry8aRJTq9rhdP2tWFSPF" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/swO46nP9oXELiLILalHZ" alt=""><figcaption></figcaption></figure>

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.


# Material Setup And Testing

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`

<figure><img src="/files/2vMikPMdM77j92CMJ6hn" alt=""><figcaption></figcaption></figure>

Navigating to `Add-ons` and then `glTF 2.0 format` and enabling `Shader Editor Add-ons`

<figure><img src="/files/ksu3k1lNg85cLWdBtaTc" alt=""><figcaption></figcaption></figure>

This will allow us to export occlusion maps if we have them.

Switch to the `Shading` tab to begin making our first material.

<figure><img src="/files/Q7TJvRzTDIw761i6oInB" alt=""><figcaption></figcaption></figure>

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`.

<figure><img src="/files/c3A5BwqU8NgNaObXw0uq" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/oKTGTzljIyVCcEVjcKR1" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/23OhzvsighzNaBZEnXhB" alt=""><figcaption></figcaption></figure>

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!

<figure><img src="/files/N64W99xS5RcUMgRBKeNB" alt=""><figcaption></figcaption></figure>

Any specific material settings should be tested at this point to help inform what values any texture maps you create should follow.

#### Note about Transparency

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`

<figure><img src="/files/1MB5stczp5Tt6UvvOBym" alt=""><figcaption></figcaption></figure>


# Pipeline Planning

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.
  * run the exported GLB through the Avatar Tools CLI

### Installing Avatar Tools CLI

* Download the standalone Binary for node.js v20.11.1
  * Windows <https://nodejs.org/dist/v20.11.1/node-v20.11.1-win-x64.zip>
  * Mac <https://nodejs.org/dist/v20.11.1/node-v20.11.1-darwin-x64.tar.gz>
* Extract the .zip and place it in a folder relative to your blender file
* Change the folder name from `node-v20.*` to `node_bin`
* Download the `.zip` from <https://github.com/mml-io/avatar-tools>

<figure><img src="/files/Ucpd9wXeqTtDMR4bxjsh" alt=""><figcaption></figcaption></figure>

* 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%`
  * Mac/Linux `export PATH=../node_bin;$PATH`
* run `npm install`
* run `npm run build --workspace packages --workspace clis --workspace tools`

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.

```python
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) 
    
    
```


# Asset Production and QA

We cover best practices for asset production and QA in [Asset Production Best Practices](/odk-docs/characters/creating-a-custom-character/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.

<figure><img src="/files/uUYqf0z1QuS0VJaJY3xf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/7Lsv3DmF3aC1CNB9vj7R" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/3VAzxu8OsJcBcOC7pyCu" alt=""><figcaption></figcaption></figure>

## QA

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 [Further Testing](/odk-docs/characters/creating-a-custom-character/creating-a-simple-avatar-collection/further-testing) 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.


# Asset Production Best Practices

* [General Approach 3D Asset Production](#general-approach-3d-asset-production)
* [Generalized QA Approach](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Generalized-QA-Approach)
  * [Load GLBs into an M2 Editor](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Load-GLBs-into-an-M2-Editor)
  * [Check Textures](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Check-Textures)
    * [Common Issues:](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Common-Issues%3A)
  * [Check for Overlapping Polygons](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Check-for-Overlapping-Polygons)
  * [Check Skin Weighting](https://yuga-labs.atlassian.net/wiki/spaces/OSE/pages/544931843/Third+Party+Avatar+Guidelines#Check-Skin-Weighting)

## Production Guidelines <a href="#production-guidelines" id="production-guidelines"></a>

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.

### General Approach 3D Asset Production <a href="#general-approach-3d-asset-production" id="general-approach-3d-asset-production"></a>

**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** tool in Maya, follow these steps:

1. **Select the Mesh**:
   1. 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.
2. **Open the Mesh Cleanup Tool**:
   1. Go to the top menu and navigate to **Mesh** > **Cleanup**.
3. **Adjust Cleanup Options**:
   1. The **Cleanup Options** window will pop up. Here, you can choose specific issues to clean up. The options include:
   2. **Remove Geometry**: For deleting specific types of geometry like non-manifold edges, lamina faces, zero area faces, etc.
   3. **Tessellation Cleanup**: For correcting tessellation problems, like polygonal faces with more than four edges.
   4. **Repair Geometry**: Fixes issues such as zero length edges and zero geometry area.
4. **Set the Operation Mode**:
   1. **Select Matching Polygons**: This highlights the problematic areas in the mesh without making changes, allowing you to see where issues exist.
   2. **Cleanup Matching Polygons**: Automatically fixes the problems based on the criteria you set.
   3. **Cleanup Non-Planar Faces**: Specifically targets and fixes non-planar faces.
5. **Execute the Cleanup**:
   1. Once you've set your options, click **Apply** or **Cleanup** to execute the operation.
6. **Review the Results**:
   1. 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.

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**

1. **Detail Accuracy**: Are the high-resolution details well-defined and true to the concept art?
2. **Subdivision Levels**: Are the subdivision levels managed correctly to provide smooth transitions between levels of detail?
3. **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:**

<figure><img src="/files/Er3YPgpKUSfSyLRbYGjT" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/xjF0QPFE9mvMtMjp4DGc" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/8OkU7FOhlyXG0VznVzNV" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/elBsQBJX9errohqDBvpj" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/m3Dr8qF9Yi9QLiRnxAml" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/tFbQd6m2nVvZDnWR6bG2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/qMP7GWR0PFo5flo8VHyC" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QW6OANpNW1yXPlplWgyi" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/2D4xNH5b33IgKsO1ezXb" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/iA6L34HMvWgzHLn3Nccs" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/rIhpeMrZzECmI5CTEn0g" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/VuUeV9uOjlh43GubnuwU" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/6F78u29q4b75htfEOIXg" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/o3afVzAakg3Q4sork4Km" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/83ajdCbAgiaGGXbjiqbS" alt=""><figcaption></figcaption></figure>

**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?

<figure><img src="/files/Fao9zl4YDmkUrknAKe0h" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/bePoEmIZIlPgtlSvermc" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/cqinmcoPzIYV44D5Mn7U" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/jdGkyp8JGxh3ZFuLXFNv" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/9dSU3jnft9eFm46qnWw2" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/I6Dodiem3bYSDvGMkSFU" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/VLArFgligr83uHrDUx8I" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/lH9SsDPWLMBLSu3wU4l3" alt=""><figcaption></figcaption></figure>

**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:

1. **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.
2. **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.
3. **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.
4. **Ignoring Edge Flow**: Proper edge flow is crucial for smooth deformation, especially in areas that bend or stretch. Avoid creating random or chaotic edge loops that don’t follow the natural contours of the model.
5. **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.
6. **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.
7. **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.
8. **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.
9. **Neglecting UV Layout Considerations**: Retopology should consider the eventual UV layout. Avoid creating complex topology that would make UV unwrapping difficult.
10. **Overlapping Geometry**: Ensure that there are no overlapping vertices, faces, or edges. Overlapping geometry can cause issues in rendering, texturing, and animation.

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.**

<figure><img src="/files/sdVdl3V33nq5oSdeNF2e" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/k4yvXw18b9nu04QB9Auh" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/DppSIUvoL85E2uQs90xS" alt=""><figcaption></figcaption></figure>

**Texturing (Hand painted)**

<figure><img src="/files/hOMgqvD1woFXwwnJTQeh" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/yOrnCSB5aoHCN6jW2ojw" alt=""><figcaption></figcaption></figure>

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:

1. **Understand the Style:**
   1. Art Direction: Know the style you're aiming for. Hand-painted textures often emphasize stylization over realism, with exaggerated colors and simplified details.
   2. Reference Gathering: Collect references of the style you're targeting, whether it’s cartoony, fantasy, or another specific aesthetic.
2. **UV Mapping:**
   1. Efficient UV Layout: Ensure your UVs are well laid out with minimal stretching. This will make painting easier and more accurate.
   2. Texture Space Optimization: Use as much of the UV space as possible to maximize texture resolution, avoiding empty areas in the UV layout.
   3. Consistent Texel Density: Maintain consistent texel density across the model, so the details are uniform in size.
3. **Base Colors and Gradients:**
   1. 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.
   2. Gradient Application: Apply subtle gradients to avoid flatness. Gradients can suggest light direction or simply add visual interest.
4. **Painting Details:**
   1. 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.
   2. Material Definition: Clearly define different materials (wood, metal, cloth) by painting in their characteristic details and surface qualities.
   3. 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.
   4. 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.
5. **Color and Contrast:**
   1. 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.
   2. Contrast: Add contrast by differentiating between light and dark areas. This adds depth and helps the texture read better at a distance.
6. **Detailing and Wear:**
   1. 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.
   2. Edge Highlighting: Use edge highlights to emphasize the edges of objects, making them stand out more.
   3. 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.
7. **Avoiding Procedural Looks:**
   1. Hand-Paint Everything: Avoid procedural or automatic tools as much as possible. The charm of hand-painted textures comes from their personal, crafted feel.
   2. Break Repetition: Add small variations to prevent patterns from looking too uniform or repeated.
8. **Final Touches:**
   1. Texture Review: Continuously review the texture on the model to see how it looks from various angles and distances.
   2. 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.
   3. 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.

**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?

### Generalized QA Approach <a href="#generalized-qa-approach" id="generalized-qa-approach"></a>

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.

#### Load GLBs into an M2 Editor <a href="#load-glbs-into-an-m2-editor" id="load-glbs-into-an-m2-editor"></a>

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 [GitHub - rdeioris/glTFRuntime: Unreal Engine Plugin for loading glTF files at runtime](https://github.com/rdeioris/glTFRuntime) to load GLBs into unreal. You can load an individual GLB into the editor by dragging and dropping into the content browser.

#### Check Textures <a href="#check-textures" id="check-textures"></a>

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.
  * 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.
* 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.

#### Check for Overlapping Polygons <a href="#check-for-overlapping-polygons" id="check-for-overlapping-polygons"></a>

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.

#### Check Skin Weighting <a href="#check-skin-weighting" id="check-skin-weighting"></a>

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.

<figure><img src="/files/v8iJ0oB7DfLfnwyH0MFR" alt=""><figcaption></figcaption></figure>


# Trouble Shooting

### MML format is correct and the GLB is valid but it is not loading in the Otherside.

Double check that the MMLs are encoded with `UTF-8` and not a something like `UTF-8 with BOM`.<br>

### Avatar prepared but not loading in ODK/Otherside

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)


# Making Avatars Available in the 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.

## 1. Upload GLBs to a Publicly Available Location

To make avatars available to all your holders they must be publicly available. They will be downloaded from the storage controlled by you into the Otherside when a user requests an avatar.

## 2. Create and Upload MMLs to a Publicly Available Location

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:

```
<m-character src="https://fake-collection-api.io/glbs/1.glb"></m-character>
```

You should create 1 MML for each token in your collection.

## 3. Update collection metadata to include an MML Field

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:

```json
{
        "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"
            }
        ]
    }
```

This is what allows Otherside games to pull in a users avatar that they have in their wallet.

## 4. Contact Yuga to Whitelist your Contract

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://www.otherside.xyz/avatars/intake-request>.

Once your form is filled out, please [join the Otherside discord](https://discord.gg/the-otherside) 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.


# Metaverse Markup Language

## Overview

This is a curated list of resources ([originally assembled by Directive Creator](https://github.com/DirectiveCreator/awesome-mml)) for Metaverse Markup Language (MML): tools, docs, editors, and more.

[MML](https://mml.io/) is Metaverse Markup Language, developed by Msquared.

### MML Basics

1. [**Metaverse Markup Language (MML) - Official Site**](https://mml.io/)**:**\
   Open-source web technologies for building multi-user metaverse experiences using HTML and JavaScript.
2. [**MML Documentation**](https://mml.io/docs)**:**\
   Comprehensive guides and references for MML elements.
3. [**MML Discord**](https://discord.com/invite/hPdWYhfVem)**:**\
   Join the community on Discord to discuss MML and connect with other developers.
4. [**Main MML Repository**](https://github.com/mml-io/mml)**:**\
   Metaverse markup language for describing 3D multi-user interactive metaversal objects and experiences based on HTML.

### Building with MML

* [**MML Editor**](https://mmleditor.com/)**:**\
  An interactive editor for building and testing MML documents.
* [**3D Web Experience**](https://github.com/mml-io/3d-web-experience)**:**\
  Packages to run web-based, multi-user 3D web experiences supporting MML.
* [**Esbuild Plugin MML**](https://github.com/mml-io/esbuild-plugin-mml)**:**\
  An esbuild plugin that bundles JavaScript/React sources into HTML documents for MML.
* [**MML React Starter Project**](https://github.com/mml-io/mml-react-starter-project)**:**\
  Example of a server that serves a live MML document using React.
* [**MML Starter Project**](https://github.com/mml-io/mml-starter-project)**:**\
  Example server for serving live MML documents via WebSocket.
* [**MML Guided Tour**](https://github.com/mml-io/mml-guided-tour)**:**\
  Implements an MML Guided Tour with live, multiplayer experiences.
* [**MML React Space**](https://github.com/mml-io/mml-react-space)**:**\
  Uses MML, 3D Web Experience, and React for interactive 3D web experiences.
* [**MML Playground**](https://github.com/mml-io/mml-playground)**:**\
  Minimal 3D playground powered by MML for creating live, multiplayer experiences.
* [**MML Starter Project Overlay**](https://github.com/mml-io/mml-starter-project-overlay)**:**\
  UI overlay for quick access to MML resources and project sharing.

### MML Resources

* [**MML Blog**](https://mml.io/blog)**:**\
  Stay updated with the latest news, tutorials, and announcements related to MML.
* [**MSquared Blog**](https://msquared.io/blog/)**:**\
  News and updates from MSquared, with frequent MML-related updates.
* [**Introducing Crowd Support in Web Worlds**](https://msquared.io/blog/introducing-crowd-support-in-web-worlds)**:**\
  Mass concurrency and Crowd Support in MML.
* [**AI-Powered NPC with MML**](https://mml.io/blog/ai-powered-npc)**:**\
  Guide on integrating AI to power NPCs using MML and OpenAI.
* [**MSquared Atlas**](https://atlas.msquared.io/things)**:**\
  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.
* [**Open Page**](https://other.page/)**:**\
  Fan, engagement, and loyalty platform with MML support.

### Examples and Exploration

* [**MML Examples**](https://mml.io/examples)**:**\
  Demonstrations of MML capabilities and project templates.
* [**MML Editor Explore**](https://mmleditor.com/explore)**:**\
  Explore examples and code snippets within the MML Editor.
* [**3D Web Experience Examples**](https://mml.mgz.me/)**:**\
  Examples showcasing the 3D Web Experience with MML by TheCodeTherapy.
* [**Crazy Run 2**](https://directivecreator.com/crazyrun2)**:**\
  Obstacle course game with leaderboard and achievements, created entirely in MML.

### Streaming

* [**Live Streaming to the Metaverse**](https://mml.io/blog/live-stream-to-the-metaverse)**:**\
  How to stream events inside of MML using Cloudflare.
* [**Cloudflare Stream**](https://www.cloudflare.com/developer-platform/products/cloudflare-stream/)**:**\
  Service for hosting streams integrated with MML.
* [**Dolby OptiView**](https://optiview.dolby.com/)**:**\
  High-quality live streaming and playback service.
* [**OBS (Open Broadcaster Software)**](https://obsproject.com/)**:**\
  Free and open-source software for video recording and streaming, compatible with MML setups.

### Somnia

* [**Somnia Playground**](https://playground.somnia.network/)**:**\
  A hub for creativity and immersive experiences using MML.
* [**Somnia Blockchain**](https://somnia.network/)**:**\
  High-speed EVM blockchain with native MML support.

### On-chain and State Services

* [**Etherbase**](https://github.com/msquared-io/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.

### Avatars and Rigging

* [**GLTF Avatar Exporter**](https://mml-io.github.io/avatar-tools/main/tools/gltf-avatar-exporter/)**:**\
  Tool for exporting avatars in GLTF format for use in MML.
* [**MML Avatar Tools**](https://github.com/mml-io/avatar-tools)**:**\
  Web tool for fixing mesh, skeleton, and material issues in avatars.
* [**MML Blender Extension**](https://github.com/mml-io/avatar-tools/blob/main/plugins/blender-geometry-utilities/mml-avatar-tools.py)**:**\
  Blender extension for MML avatar preparation.
* [**Sandbox Converter App**](https://github.com/msquared-io/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.
* [**Avatar Creator**](https://github.com/msquared-io/avatar-creator)**:**\
  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.
* [**Game Rig Tools**](https://toshicg.gumroad.com/l/game_rig_tools)**:**\
  Tools for preparing game-ready rigs in Blender.
* [**Surface Heat Skinning**](http://www.mesh-online.net/shd-blender-addon.zip)**:**\
  Blender addon for optimized rigging.
* [**Avatar Documentation**](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/features-and-tutorials/avatars/creating-mml-avatars-with-blender-and-free-rigging-tools)**:**\
  Comprehensive guide and tutorial for creating MML avatars.
* [**Grillz Gang**](https://www.grillzgang.com/)**:**\
  NFT collection with MML-ready avatars.
* [**Mcbess Celmates Avatar Set**](https://directivecreator.com/mml/mcbess-celmates)**:**\
  MML Conversion of Mcbess Celmates avatar.
* [**Broadside Avatar 1**](https://directivecreator.com/mml/broadside)**:**\
  MML Conversion of Broadside avatar.
* [**Broadside Avatar 2**](https://directivecreator.com/mml/broadside2)**:**\
  Another Broadside avatar conversion in MML.
* [**Sappy Seal**](https://directivecreator.com/mml/sappy-seal)**:**\
  MML Conversion of Sappy Seals avatar.
* [**MML Avatar Starterkit**](https://github.com/DirectiveCreator/mml-avatar-starterkit)**:**\
  A quick guide and tool to get started with MML avatars at scale.
* [**MML Avatar Starterkit Web Tool**](https://mml-avatar-starterkit.onrender.com/)**:**\
  Batch web tool to generate multiple MML files from GLB URLs.
* [**Toothpaste Avatar**](https://directivecreator.com/mml/toothpaste)**:**\
  VRM to MML avatar conversion.
* [**Salt and Pepper Avatars**](https://directivecreator.com/mml/salt-pepper)**:**\
  VRM to MML Avatar conversion.
* [**Avatar Tools (online tool)**](https://directivecreator.com/avatar)**:**\
  Online tool for testing and creating avatars.
* [**Further MML Avatar Creation Resources**](https://directivecreator.com/mml/avatar-tutorial)**:**\
  Additional resources for creating avatars with MML.
* [**Blender Workflow Docs**](https://docs.otherside.xyz/platform-documentation/creation/unreal-development/features-and-tutorials/avatars/creating-mml-avatars-with-blender-and-free-rigging-tools)**:**\
  Detailed guide for creating MML avatars using Blender and free rigging tools.
* [**M-Character Reference**](https://mml.io/docs/reference/elements/m-character)**:**\
  A detailed reference for M-Character elements in MML.
* [**Video Tutorial**](https://www.youtube.com/watch?v=0m5xAzhoGkQ)**:**\
  A video tutorial covering avatar creation techniques.


# Rules of the Road

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.
  * 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>.


# ODK Plugin

Documentation for some of the functionality provided with the ODK plugin


# Authentication

**Glyph**

Otherside uses a user-friendly wallet called [Glyph](https://useglyph.io/). 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: <https://www.privy.io/>

**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 [WebApp](https://launch.otherside.xyz/) 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.

#### Account management

To access and manage your Glyph account, click on the profile button in the top-right hand corner.

<figure><img src="/files/jcW8lLkpXmXKAp29vyyu" alt=""><figcaption></figcaption></figure>

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](/odk-docs/odk-plugin/wallets).


# Web Browser

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_ODK_WebBrowserWorldService` .

Pressing "P" 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.

Avatar

* 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<br>

For information on how to use the web browser to open links to your web pages, see the section on Boneyard Example Content [here](/odk-docs/odk-templates/boneyard-template/arcade-machine).<br>

<div><figure><img src="/files/O4PvXq77YT85u4tJso0z" alt=""><figcaption><p>Example of linking to an external page</p></figcaption></figure> <figure><img src="/files/TvkTDMHqmknmymtxbK2X" alt=""><figcaption><p>The profile Overlay that opens when pressing TAB</p></figcaption></figure></div>


# Wallets

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` provides you with the player's main glyph Wallet Address via `GetPrimaryWallet`. The primary wallet and any optional additional linked wallets are available via `GetLinkedWallets`.\
`BP_ODK_WalletWorldService` provides general token manipulation and inspecting functionality.

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 [Wallet Views](/odk-docs/odk-plugin/wallets/wallet-views).


# Wallet Views

### Overview

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.

<figure><img src="/files/zStsJpl3d6h0YSYbQSzL" alt=""><figcaption><p>Example of grabbing tokens in a view</p></figcaption></figure>

### Creating Wallet Views

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:

<figure><img src="/files/IzIX6lVa1TJwOf8WriBR" alt=""><figcaption><p>Example filtering logic</p></figcaption></figure>

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.

### Checking Tokens

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.

<figure><img src="/files/j8M9MP8ero7H5fKRP8lI" alt=""><figcaption></figcaption></figure>

### Binding to Wallet View Events

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.

<figure><img src="/files/Hs2B3DPWf2eeewJ18N1h" alt=""><figcaption></figcaption></figure>


# ODK Input Management

\ <br>


# UI Mode

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.

<figure><img src="/files/ypoA8EiFljXD5EllKAwI" alt=""><figcaption><p>Example of function usage</p></figcaption></figure>

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.

### Handling UI Mode Changes

`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:

<figure><img src="/files/C6JW0bRimis0SiS1XmTD" alt=""><figcaption><p>Handling of UI mode changes on the <code>BP_ODK_PlayerControllerBase</code> .</p></figcaption></figure>

If you require different logic to be perfomed when the UI mode changes, you can override the functionality is `HandleUIModeChangeRequest`.


# Input Component

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`.

{% hint style="info" %}
Note, there exists a "On Posses Input Mapping Contexts" on the base `BP_ODK_PlayerCharacterBase` . Please do not use this config. It won't work within the ODK and will be removed in a future iteration.

<img src="/files/r9bidLD2M2i0O5E8RW6E" alt="" data-size="original">
{% endhint %}


# Character Input

The default IMCs used by derived `BP_ODK_PlayerCharacterBase` assets can be configured by overriding the `GetDefaultInputMappingContexts` 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.

<figure><img src="/files/xco4y0PLR4tNkhtgcfuF" alt=""><figcaption><p>Example of <code>GetDefaultInputMappingContexts</code></p></figcaption></figure>


# Widget Input

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.


# Configurable Input Action Keybindings

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.

<figure><img src="/files/oqvKQREDaqCMR1KoRE56" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/ub3faTWKyVa06jJTkfhA" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/JuEvfE3qSwn6vYY92WBp" alt=""><figcaption></figcaption></figure>


# ODK Widget

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).


# ODK Base UI

**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.

***

### 🔁 How It Works

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)

<figure><img src="/files/eDa6RJdE0IeMUhSkZEeB" alt=""><figcaption></figcaption></figure>

***

#### 🎮 Control Executors

The Executor is a Blueprint class that drives the behaviour of a control. Every executor supports the following functions:

| Function                | Description                                                                                                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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). |

This system makes each control **self-contained** and keeps your UI logic clean and reusable.

***

#### 👁️ Visibility Executors (Optional)

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.

***

#### 🏷️ Registered Control Name

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)

***

### 🧰 Runtime Editing

The Blueprint Function Library `BPFL_ODKBaseUI` gives you helpers for modifying UI areas at runtime.

Notable functions:

| Function                               | Purpose                                                                                      |
| -------------------------------------- | -------------------------------------------------------------------------------------------- |
| `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                                                          |

This system is especially useful in:

* Hot-swapping controls during a session
* Extending shared base widgets without making duplicates

***

## Examples

### 🧪 Example: Roles selector

* **Control Type**: Dropdown
* **Executor**: `BP_SettingsExecutor_PlayerRoles`
  * `GetCurrentValue` → returns a string array of available roles
  * `HandleValueUpdate` → sets the players new role
* **Visibility Executor**: `BP_ODK_UIControlVisibility_UserHasSpecificRole`


# ODK Interaction

Documentation regarding ODK interactions

The ODK interaction system facilitates interactions between the player character and in world props.<br>

<figure><img src="/files/CX7GfwFg2478hvRJ7CcU" alt=""><figcaption></figcaption></figure>

## Basic Interaction Flow

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 `BPC_ODK_InteractableComponents` the player can interact with in the world. When the player inputs the "Interact" input action event, the `BPC_ODK_InteractionComponent` will inform the currently interactable `BPC_ODK_InteractableComponent` that it needs to execute it's logic.

## Setting up a new Interaction

If 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<br>

<br>


# Token Gating

Used to lock features behind definable conditions, often regarding token ownership.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfO5atdR1ne8-G1RSfbY8mrzZlMvHs9rJpufadHCeWR7thAvm6BCXKLONPZ52p7WCBN4_7OsNSI-nDBjUV0Bt2hxmj-TNQHLw5u0LbBWD5mUNkjWmdmdOW2adSzB9rELdItSFvDnSZ00LdhmPZz6q9-7t4?key=3WvQRPZVTqRxDfJK6HfL8Q" alt=""><figcaption><p>An example of a Token Gate</p></figcaption></figure>

In-world gating uses the base class `BPM_GatedAreaBase`.

{% hint style="warning" %}
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.
{% endhint %}

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

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.

<figure><img src="/files/HplHjEq0zzKPvGKXsP7i" alt=""><figcaption></figcaption></figure>

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).

<figure><img src="/files/nOcyZ5KJ1x2Tqg2mHkfD" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/4WFNdctGGQ0c9d4ZACtt" alt=""><figcaption><p>An example of overrideing <code>IsConditionSatisfied</code> to check whether a player has any token from an array of configurable NFT Chins.</p></figcaption></figure>


# ODK Video/Millicast Screens

Documentation for ODK Video/Millicast Screens

<figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXePnj7WOYF5QwBBQG-LaJ9CQ8DbdptC7EJIGF0FkVJiYjzBQUVzgRLOiDdgvxL7t5hC9Ha1em5Ny7oNNY5exZkiwMJRjvNv5eQB9T0ilmAOj4FRoNejidQY9xdDN-se380uEDTQSEVPCL2GQQfzlY9Ymtg?key=W2-ZMNBPiGgQoccqzXSqMg" alt=""><figcaption></figcaption></figure>

<br>

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.

<br>

The important function to consider here is “GetMeshComponentToDisplayOn” and should be overwritten to define a static mesh component and MaterialIDs

<br>

<figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXedTM8vT8lpR0XYOM4w1x5OE7byYUiSw_SsMRQDm5T2yxtGnaqfEYkCnlwYN0YjhnvHEgdV3t6i2a5N6bdU5FHSl3QWsua2MbJEMGc5K02dZWWeGRZKRosRGYLbRjfR0RnI6vMhu_shzg7HaM2cYabOc6Yp?key=W2-ZMNBPiGgQoccqzXSqMg" alt=""><figcaption></figcaption></figure>

<br>

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.

\ <br>

## BPM\_VideoPlayerWithMillicastBase

The base class contains several configurable variables to help with the setup of the videos and millicast.

<br>

<figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcUnb86bHhm36aWxk6UYGkQ5eDsb05k5nDJzfI9AIZ3aWK-cqGlEShpSPieuJjou9vGn4XsNptFfBMPsXfwo6m0d0HebyntAr4aulDB2v5IR-aUrpd3D3H1gTJVa1dj774N1rDsa85w6Gjc5MsnmAXRAPKx?key=W2-ZMNBPiGgQoccqzXSqMg" alt=""><figcaption></figcaption></figure>

<br>

The Receiver name will be used on the Millicast Control panels to set which screen to display millicast content on.

<br>

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.

<br>

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.

<br>

<figure><img src="/files/swAi7lJVX052bd48hjD7" alt=""><figcaption></figcaption></figure>


# ODK Koda Cam

Selfie Cam

![](/files/QO8KJH3FcprsIC059Nfh)

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\_ODK\_ScreenshotComponent**

A component attached to the `BP_ODK_PlayerCharacterBase`, responsible for toggling selfie camera mode. It spawns the various `BP_ODK_CameraActor` camera actors to take different shots.

**BP\_ODK\_SelfieCamera**

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.

### Using the Selfie Cam <a href="#id-3tcxig5qpz99" id="id-3tcxig5qpz99"></a>

<mark style="background-color:$danger;">**Important Note**</mark><mark style="background-color:$danger;">: The BP\_AttachmentManagerODK singleton must be defined in your level's world settings > Non Morpheus singletons section.</mark>\ <mark style="background-color:$danger;">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.</mark>

The Koda Cam can be activated using the `IMC_ODK_KodaCam` Enhanced Input Mapping with the following actions:

**IA\_ODK\_KodaCam\_Toggle** - Enable/Disable the Selfie Camera

**IA\_ODK\_KodaCam\_Rotate** - Rotate the camera when active

**IA\_ODK\_KodaCam\_Zoom** - Changes to field of view to simulate camera zoom

**IA\_ODK\_KodaCam\_TakePhoto** - Save the image to disk

### Live Config <a href="#ac237jldyiug" id="ac237jldyiug"></a>

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.ScreenshotCam.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.


# Scannable Objects

<figure><img src="/files/ZZQ24sSdTZwtdUlF7yTW" alt=""><figcaption></figcaption></figure>

## BPC\_ScannableComponent 📸🔍

The `BPC_ScannableComponent` 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.

***

### System Overview 🛠️

When the `BPC_ScannableComponent` component is attached to an actor, the Selfie Cam performs the following checks:

1. **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.
2. **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:
     * **Scan Range** = `1000 units`
     * **Required Points** = `1` (i.e. only one point needs to be inside the camera view).
3. **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.
     * 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).

If an actor passes all checks, it is marked as **scanned** for that screenshot.

***

### Metadata Inclusion 🗂️

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\_ODK\_KodaCam\_TakeSnapshot**.
  * Screenshot is saved locally and minted.
  * Delegate fires → returns metadata including any scannable actors in view.

***

### Customization ⚙️

The **BPC\_Scannable** component exposes several options for tailoring how actors are scanned and represented:

#### Scan Parameters

* **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.

#### Metadata

* **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_ScannableComponent` and override the `GetScanInfo` function.
  * This allows returning a custom JSON object with arbitrary fields.
  * Example: rarity, quest state, or any game-specific attributes.

#### Bounds Modifiers

* **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.

***

### Example Workflow 🚀

1. **Setup**
   * Attach `BPC_ScannableComponent` to any actor you want to be detectable in Selfie Cam mode.
2. **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).
3. **Take Screenshot**
   * Player enters Selfie Mode (`IA_ODK_KodaCam_Toggle`).
   * Camera checks for nearby scannable actors.
   * If visible, bounding box points pass frustum/trace tests → actor metadata is collected.
4. **Handle Metadata**
   * When the screenshot is taken, the `On Screenshot Taken` delegate on `BPC_ODK_ScreenshotComponent` fires.
   * This delegate returns:
     * The screenshot image data.
     * Any attached metadata from scannable actors.

***

👉 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.


# Send APE Coin to another player (Deprecated)

In order to start the flow by which you can send APE coin to another player, you will need to call the `SendApe` function on the `BPMC_TransferComponent` that exists on your local player `AMorpheusActor`. You will need to pass in the `AMorpheusActor` of the player you would give APE coin to.

<figure><img src="/files/7hP4BDKD2FcJsg3ZyP9z" alt=""><figcaption><p>An example of calling SendApe that will promp an input screen to the player and start the process.</p></figcaption></figure>

## Configuration:

The `BPFL_TransactorUrlManager` is the API user to get transactor Urls. This assets needs to be modified to suit your transactor's endpoints and params.

`WPB_TransferBrowser` will also have to be modified so that it closes at the appropriate when the transactor is no longer needed. Currently some example logic exists that might reflect what the end logic looks like.

<figure><img src="/files/xb4Ad6hHbwkGrGGiznyO" alt=""><figcaption></figcaption></figure>

`StartSendApeAction` will be called on the `WPB_TransferBrowser` whenever the transactor is needed to be used to facilitate sending ape. The `ExternalUserId` and `DelegatedToken` will provided here as arguments to aid in constructing the transactor URL.

<figure><img src="/files/to5i1HluK2BGtLwdDPLr" alt=""><figcaption></figcaption></figure>

`StartBuyItemAction` will be called whenever the transactor is needed to be used to facilitate the buying of an item. A `ItemId` and `DelegatedToken` will provided here as arguments to aid in constructing the transactor URL. Depending on what the transactor needs as params in the URL, we may need to modify the `ItemId` property.

<figure><img src="/files/moJsa03uWfOk7X4sjDWM" alt=""><figcaption></figcaption></figure>


# ODK Notifications

<figure><img src="/files/6QvJnUBHm80XMvdKMuQG" alt=""><figcaption></figcaption></figure>

The ODK notification system allows for developers to display notifications on a players HUD.

## The Notification Singleton

By default, the `BPM_ODK_NotificationsSingleton` 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.

## Setting up the HUD widget

In order to display notifications on-screen, you must implement a notifications widget on your WBP\_HUD. An example of this is the `WBP_ODK_NotificationsDisplay` 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_ODK_NotificationsBase`.

## Custom Widgets

`WBP_ODK_NotificationsBase` is intended to be used alongside `WBP_ODK_NotificationsDisplay` 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.

## Sending Notifications

Notifications can be sent using the helper functions in `BPFL_ODK_Notifications`.

### Send ODK Notification To Local Player

<figure><img src="/files/SQ5EFXXulv4ddRnfnvIz" alt=""><figcaption></figcaption></figure>

This function sends a notification to the authoritative player with the following payload:

#### Notification Type

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.

#### Notification Time

How long to display the notification on-screen if it is not dismissed by the user.

#### ODK Notification Struct

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.

<figure><img src="/files/Yn8z4A9AUiBNa0iS7Eh3" alt=""><figcaption><p>any struct type can be packed inside this Additional Data object</p></figcaption></figure>

<figure><img src="/files/0y3wtIpdRE4korW7nNSy" alt=""><figcaption><p>This shows the struct being unpacked after being sent as a notification.</p></figcaption></figure>

### Send ODK Notifcation To All Players

<figure><img src="/files/1MAxUWQOJh7UQnIPQaTU" alt=""><figcaption></figcaption></figure>

This node takes in the same parameters as the local notification, but will be displayed on all users screens.


# Task Flow

### 🧠 Conceptual Overview

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:

***

#### 🔁 Triggers

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

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.

***

#### 🎯 Tasks

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

**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.


# Setup Guide

This guide walks through how to set up a new flow.

***

#### 🎭 Step 1: Place the Task Flow Actor

Start by placing a `BP_ODK_TaskFlow` actor in your level.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcqTrPfNCkl7E76BZOWQrgq4pZIIUm5eVd-1yUJ-SknaGaGnb4x9qccVZSd1GwxCK5EJRmsdgSQhXuk_qt76eawKJmR0l3QN-xtFtwOLqCQ2z8VZCwgqyaPUT7rj3UFNlGBSHg8EQ?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption></figcaption></figure>

This actor is responsible for:

* 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

***

#### 🎯 Step 2: Assign a Flow Trigger

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeKSK5r5swyFNQX-mpj4eVYx2pTyaSTPeGP-HKbiCbzpWeDY2VEXlgjmWbKKjq6Mk4-ZGuFtL5DTY6zTGFMRYb65v3bpstuTGoqJvCOyX24-F9ntNAXUojsZlOmFEyc2vtAI7CPlg?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfJonw5xyJd02OyHGUQ__otA0R94C9NBrCgd_YPpdiU6mSSk8-fhRZvD9jX2Gwndu-KH_ZLBTCGA_b8U2kgYP3ODUT3Nxc6rCRaNpQ7sjftqL_pcwUKNHfmVaiueaSJba9nZ9-Chw?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption></figcaption></figure>

Once your trigger logic finishes, it must call `FlowTriggerCompleted`

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXer3JiJtQy_827Y6W95s8xCl4h-CMxtq78LSOjZNSqqzifYkVC3uDfTPE0-Ad_DK8FLuEMl6UCFgBVAaJ4lfLK0gtotjEGOof8o6b7zIcC3Yg4dUwenWdMt2W2x8DrtyG0J_23EPw?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption></figcaption></figure>

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.

***

#### 📦 Step 3: Create and Configure the Task Flow Data Asset

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)

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe3ObWtezp1uU-v2Vl_n9VdrWYYQYJQj2W-mcA0Tg78N8eOpWqasYWX0cUwki2bZp2325M3bq7MRdgEL1nQBg-PocRS6TScpmeAId6uulM942lkT11Pjcqzm6Jy8eFAfdbkQ8hLSQ?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption></figcaption></figure>

<figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfnL9HRY_sTogi2l_nzJbMBMKn6vU7wrF-6XVCWoKzkUBJ5SNwyWnlDPyiAJDtC63l48rqeEYYzgfLdh2SX2e2q7H5r27V4XhFhwaHUmIGSRrA3UyR97nc1B_5lgGbx-iof2-8P8A?key=1ZB-XMHXMCizSl9hJmmyTg" alt=""><figcaption><p>Example of a condition executor</p></figcaption></figure>

* **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.

***

#### 🧩 Step 4: Define Each Task

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

***

#### 🔁 Step 5: Add Retry Logic (Optional)

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.

***

#### ⚙️ Step 6: Add Actions (Optional)

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.

***

#### 🧪 Debugging

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.


# Switching To Preview ODK Versions

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&#x20;**<mark style="color:purple;">**Otherside Login**</mark>**, and NOT Msquared Login**

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXe7zQJAy3rXzSRFXXF5IjEis9djiU9ZBrst-Ys3cXZ1tUV0YoNx3kt4HTtjlw6uEBS4l8f9axE7SDN08XXdvyFQH1UwLrmERV_e2ltzOS0tp8e2mWHXxu0A00XtjHV22TDF_YR12g?key=7duB6OmCLB1jpVD4MO4zPQ)

**2. Select&#x20;**<mark style="color:purple;">**ODK Alpha**</mark>**&#x20;from the web project drop down**

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXeJWl98Vurk24Ey4SKSor67uA1_JoM_Wz-NK1MV9MjZpVO-r99e0vagWqixNPVAUYSa4GpFqX-9Tpaq1pm1Ql3h2tp-PVhuFYsLTB7_BLUiJdDNggV0Hfv7AIwJOi3h-auCLXjhlw?key=7duB6OmCLB1jpVD4MO4zPQ)

<br>

**3. Click the settings cog in the top right and select&#x20;**<mark style="color:purple;">**Preview**</mark>**&#x20;in the update channel drop down**

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXc3qPBA3ZfHGJbL_ZulJbSFDsv97PYApliOhhZ6UidjwC0oV5hl1td-C1z7orJOQgAdvF6WEY1-iegZj8Y7gfbjYxevEBKb8_uXzh97ouSCWTbjnsz2lFrXZEQoMjEpxRZgLfX5dQ?key=7duB6OmCLB1jpVD4MO4zPQ)

<br>

**4. Click the Top left button that loads the launcher version installer menu**

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcLrq7cWAPoHadPqNvx08R8BnHE39ivCK9zIzJtzuzld_PMpXSiPpJhJtwNgPzVkqf7OlGpMggqkzaiIP15rYxMGJBp8GD4KqOVwuXtzLjBwyAkuJIqy6DDFlv6jZ6jQjAzmflf?key=7duB6OmCLB1jpVD4MO4zPQ)

<br>

**5. Click&#x20;**<mark style="color:purple;">**Add Installation**</mark>

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXcAtyF-hHm4BFI4gpqRoqayOt67bE0YXbv8-1iYNSamsA-czEsGaFx0jwGYDXBshDM_gXQJZykGAq9CX8M2noX0vkp7oqX1HVUsKbrLaJJPwtQp6jtnbNaifrDrRH9tHPHI-SBV?key=7duB6OmCLB1jpVD4MO4zPQ)

<br>

**6. From the version dropdown select&#x20;**<mark style="color:purple;">**ODK Chapter 8.1 Preview**</mark>**&#x20;and click&#x20;**<mark style="color:purple;">**Install**</mark>

![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXfpqKWOtfP9FICe43ieH7BmvedrTr1fDJIR9AAwJwd_rSWpDkCnN4QZZLRfAj6syLEeFw0EU0ObuDxgePsKJ_H-63LrEkdK1cwJJCKVljWBoxQqNBE2IhX_tSyfCp0qACm7SL-fCQ?key=7duB6OmCLB1jpVD4MO4zPQ)

<br>


# Persistence

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.

```
"high_scores" : "{"simon": 10, "david": 3}"
```

### The Persistence Service

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\
\- ReadJsonObjectValue\
\- ReadStringValue\
\- ReadMultipleStringValues\
\- RegisterIntValueInterest\
\- RegisterJsonValueInterest\
\- RegisterStringValueInterest\
\- SaveIntToPersistence\
\- SaveJsonValueToPersistence\
\- SaveStringToPersistence

When interfacing with these functions, you will need to pass in a string "Key" parameter and an enum "Scope" value. The scope parameter allow you to limit the access of this data. The enum has these values:\
\
\- World: Only players on this world have access to this key's persistence data\
\- Project: Players can access the persistence data on any world that share the same project.\
\- Organization: Players can access the persistence data on any world that share the same organization.

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.

### Server and Client Stores

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.


# Emotes

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`.

### Defining Emotes

You can define an emote by creating a new PDA\_ODK\_Emote asset.<br>

<figure><img src="/files/FLCvPFvBJMRe4XNepsU2" alt=""><figcaption></figcaption></figure>

**Emote Executor -** This is an instanced object in which you can configure your emote. The executor object itself will contain logic as to how an emote should behaive. This allows emotes to have varied and complex behaivour supporting things such as "feels" flipbook emotes and coop emotes.\
\
All emote executors must contain the following data.\
\
**DisplayName** - Display name used in UI\
**EmoteId** - Used to help unlock emotes via token ownership. The token metadata will specify the emote id that it represents ownership of.\
**Icon/IconUrl** - Icon used in UI, only one of these needs filling in.\
**Type** - An enum that lets code categorise emotes. Currently the types we support are: STANDARD and COOP.\
**AllowTraversal** - Whether the emotes should continue playing even if the player starts moving.

Currently, emotes come in three flavours: MONTAGE, COOP, FLIPBOOK or PICTURE. MONTAGE emotes simply play an animation on you player character. Coop emotes allow you to play animations synced with another player character. FLIPBOOK emotes allow the player to display a short 2D animation. PICTURE emotes allow the player to display a simple image. The way these are handled are very different. The emote executor allows for completely different behaviors to be executed by different emotes whilst being handled the same way in the reset of our logic.

### Starting and Stopping Emotes

`PlayEmote` and `CancelCurrentEmote` can be used to start and stop emotes regardless of whether they are standard or coop emotes.<br>


# Feels

### **What are Feels?**

They’re gifs that pop above your avatar to show emotions in Otherside. Every Feel you collect goes straight into your emote wheel.

<figure><img src="/files/9LmTuO4X5jB7ow6xzowd" alt=""><figcaption></figcaption></figure>

### **How Do I access my Feels?**

Feels can be used by pressing **'B'** to access the **Emote Wheel**.

### **How Do I get Feels**?

Feels can be earned through level progression, purchased in Otherside, or purchased via the [Amazon Store](https://amazon.com/otherside). Each **Feel** is an tradeable **ERC1155** token on [ApeChain](https://apechain.com/).

Otherside Feels:

<div><figure><img src="/files/Mvk5QTUmBB7DqseARVht" alt=""><figcaption></figcaption></figure> <figure><img src="/files/djKF8oYPQSOASRuQLe7M" alt=""><figcaption></figcaption></figure></div>


# Feels: Design

## Goal:

Start with a .gif file and end up with assets for adding a playable .gif Emote that will play in 3D space over the initiating user’s head. While other image dimensions may work, everything in use is 256x256 pixel icon animation frame dimensions which allows up to 256 frames in a 16x16 array while keeping us within 4K textures. Adhering to this should provide plenty of space and good quality for the size in game, while keeping a consistent experience between GIF Emote collections.

### Assets and Data:

1. An icon in **.PNG** format, ideally a frame from the source .gif, that will represent the Emote in the Emote Wheel. It should be unique among those existing and suggestive of the Emote content for quick and easy selection.
2. A unique **Name** for the emote. Ours are more playfully named than literal. Space for this text is limited, so a few short words is plenty else the text will be scaled down in size to fit.
3. A **Description** for the emote token that will be displayed in the holder’s inventory.
4. A spritesheet/flipbook texture in **.PNG** format that is **4096 pixels square or less**. Those currently in use are grids of 256x256 pixel frames in rows of 10 columns. Blank frames in the last row are fine. Power-of-two dimensions are not required as these are in the UI Texture space so there is no mipping of the spritesheet. Frames are clipped out of the spritesheet by a flipbook shader dividing the total area by the specified rows and columns, so anything other than a grid will display incorrectly.
5. The number of **ROWS**, number of **COLUMNS**, and the **FRAMERATE** of the .gif for playback. Rows and Columns correspond to the construction of the spritesheet, and Framerate drives the speed of the shader that advances through the spritesheet. A 60-frame .gif that plays over 2 seconds could be 6 Rows, 10 Columns, playing at a Framerate of 30. Variable framerates are not supported within a given animation.
6. Requirements for unlocking the Emote, if applicable. This system is under development but it is possible to gate access to emotes based on player wallet holdings or in-game progress.

\
With these files and data, someone familiar with the Unreal Editor can then create the Primary Data Assets required for the Emote system, and augment the data tables that link the data each Emote requires.

### Available Tools

<https://ezgif.com> was used in the production of our current **Feels** GIFs. If you are not currently starting with a 256x256 **.GIF**, you may want to begin with their Resize tool.

In the Split tools, Animated GIF Splitter will ingest a .gif file and provide a downloadable **.ZIP** file with a .gif for each frame, from which a **.GIF** can be selected to be converted to **.PNG** for the representative Icon.

![](/files/gQjKelaWQSOHC1NJmwzL)

Also in the Split tools is a **.GIF** to **spritesheet** tool. This will produce the **spritesheet** as a grid of images in the desired number of columns/rows.

![](/files/FGqBIYNyuULn1OmGXx4A)

\ <br>


# Feels: Upload

### :construction: **Under Construction** :construction:

**Feels** upload instructions will be published soon.


# Feels: Sell

### :construction: **Under Construction** :construction:

**Feels** selling will be published soon.


# Avatars

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.

#### What avatars are available?

Currently we've proved avatar implementation for the following collections:

1. Bored Ape Yacht Club
2. Meebits
3. Kodas
4. Moonbirds

There are also individual avatars meshes available for the following collections:

1. Mutant Ape Yacht Club
2. 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 [Web Browser Avatar tab](/odk-docs/odk-plugin/web-browser).

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.

#### How are the avatars implemented?

To provide dynamic access to a vast array of dynamic characters, at runtime we download and encode the relevant character meshes from their [GLB](https://en.wikipedia.org/wiki/GlTF) 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+ !

#### How is it implemented within the ODK?

Current supported avatar types are tracked within the `BPE_ODK_AvatarTypes` enum within Unreal.

If you're interested in watching for avatar updates on players you can bind to the `BPMC_ODK_AvatarComponent::OnAvatarTypeUpdated` event for whenever a user's avatar type changes. This information is available on all players, and replicated in the background. There is also a `CurrentAvatarType` on the same component which will tell you the current avatar type for that player.

#### Overriding the player profile avatar

The default behavior is for the in game avatar to reflect the configuration in the player's profile. If you wish to override the player's in-game avatar, all you need do is call `BPMC_ODK_AvatarComponent::OverrideAvatarByUrl` with the avatar URL you wish to use. You can reset the in-game avatar back to the player profile configuration using `BPMC_ODK_AvatarComponent::ClearOverrideAvatar`.


# Moderation

The ODK provide text and voice chat moderation services that allow you to moderate users within your worlds. The moderation page is available [here](https://launch.otherside.xyz/admin).

Please reach out to Yuga to request access to the moderation page.


# Analytics

ODK provides an out of the box analytics integration with [Mixpanel](https://mixpanel.com/). 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:

1. Add `BP_ODK_Mixpanel_AnalyticsWorldService` to your additional world services within your world settings.
2. Add a mixpanel project token to live config at : `project : ODK.Analytics.MixpanelProjectToken`


# Movement Modes

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" (an instance of DA\_ODK\_MovementModeId) and a movement mode component class. This allows for easy overriding and addition of movement mode behaivours.

<figure><img src="/files/uWnNF2u9kpsDYCOfjV4j" alt=""><figcaption></figcaption></figure>

A description of other useful properties:\
\- `Initial Movement Mode` - The movement mode your character will start in.\
\- `Default Movement Mode` - The movement mode that your character can default to after leaving a movement mode.\
\- `Startup Movement Modes` - Movement modes that will be initialized at the start of the game rather than created dynamically when first requested. This has very niche uses.

Each 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.

### GlidingMovementMode Example

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.

<figure><img src="/files/shbICQRwJqufm1wdkQ6R" alt=""><figcaption><p>Adding a binding for an input action</p></figcaption></figure>

<figure><img src="/files/vBmTYDvuApGQlvAY0zfg" alt=""><figcaption><p>Processing an input action to make the character jump. The "Value" param is not used but is show here for demonstration.</p></figcaption></figure>

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.

<figure><img src="/files/diyo9yzHWS6IrDvWbtI9" alt=""><figcaption></figcaption></figure>


# ODK Blueprint Node Library

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>


# Visual Effects

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.

<figure><img src="/files/J5GrDCfNDHEossNqnvWR" alt=""><figcaption></figcaption></figure>

`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` where`RemoveNiagaraEffect` is called with the relevant EffectId.

<figure><img src="/files/s1uOR6eqkNnWpfaajJXl" alt=""><figcaption></figcaption></figure>


# NPCs

{% hint style="warning" %}
This feature will only be available from ODK v10 onwards.

It is an experimental system, and is likely to change over the course of subsequent updates
{% endhint %}

<figure><img src="/files/FbwHn8awzz4N0Ar6cqld" alt=""><figcaption></figcaption></figure>

We have some systems in place in the ODK to handle creating NPCs at scale, and configuring their behavior. The classes are all in BP, so feel free to extend them, or swap them out with your own implementations if desired!

## NPC Spawning

### Initial setup

The flow for spawning NPCs using our NPC Spawning system is as follows:

* Ensure your World Settings has the `BPM_ODK_NPCSingleton` in its `AdditionalSingletons` list. This is responsible for handling "spawn requests", and managing the NPC capacity your world has

  <figure><img src="/files/hSB3PRZuIZoKx6uygdUg" alt=""><figcaption></figcaption></figure>
* Place classes extending `BP_ODK_NPCSpawner_Base` into your level (or `Always Loaded` sublevels). We have some pre-existing examples, such as:
  * `BP_ODK_NPCSpawner_Single` - spawns a single NPC at that location
  * `BP_ODK_NPCSpawner_Radius` - spawns `NumNPCs` many NPCs within a `Radius`
  * `BP_ODK_NPCSpawner_StationUser` - same as the above, but spawns NPCs that are configured to use [NPC Stations](/odk-docs/odk-plugin/npcs/npc-stations).

    <figure><img src="/files/ma4tzhwUGRqwboXc45Py" alt=""><figcaption></figcaption></figure>

#### Using Worker Clients

If you want to use Worker Clients (see [Trusted Clients](/platform-documentation/creation/unreal-development/features-and-tutorials/trusted-clients)), you will need to set them up:

* When playing in editor, you can set one of the clients to run as a Worker Client through `Editor Preferenecs -> Morpheus -> Editor Client Connection Types`
  * Each entry in the list dictates which connection type will be used for the client of that index. Each will default to `Player` (meaning a human controlled character), but you can set one to be `Worker`, to use a Worker Client in-editor.

    <figure><img src="/files/RCWJO94yWQ3s2yNYRxVA" alt=""><figcaption></figcaption></figure>
* Then, when playing in editor, increase the `Number of Players`, to include one of the above Worker Client connections.

  <figure><img src="/files/lnV9dwPsdKy449SbRvOe" alt=""><figcaption></figcaption></figure>
* If everything is configured correctly, you should see your Worker Client's window get past the "Waiting for Sign in" stage, but you can ignore the window otherwise.

  <figure><img src="/files/QgT39RhtXLN09EdW3yld" alt=""><figcaption></figcaption></figure>
* In a deployment, provided you have the appropriate permissions, you can configure the number of trusted clients (defaulting to worker clients) for your deployment via the dashboard:

  <figure><img src="/files/tpyZMttr54FFNu5XB9U5" alt=""><figcaption></figcaption></figure>

#### Live config

There are two Live Config flags that can be modified, to configure the NPC spawning:

In `Project` config:

* `ODK.NPCs.MaxNPCsPerWorker`: The number of NPCs each Worker Client can support. The `BPM_ODK_NPCSingleton` handles allocating NPCs to the worker clients according to their capacity.
* `ODK.NPCs.MaxServerNPCs`: The number of NPCs the server can support. If there is insufficient capacity on the connected Worker Clients (if there are any), NPCs will instead be spawned on the server.

{% hint style="warning" %}
NOTE: We default the `MaxServerNPCs` to 0, since we ideally want to avoid adding excess load to the server. If you want NPCs run on the server, take care with your server's performance!
{% endhint %}

### Configuring your NPC Spawners

Each NPC spawner that can be placed in the world have a few common parameters to be configured:

* `NPCBehavior`: The behavior that the NPCs spawned from that spawner will run. For more details, see [#npc-behaviors](#npc-behaviors "mention")
* `NPCClass`: The Morpheus Actor spawned by the NPC Spawner. All NPCs spawned will use this class. In most cases, this can be left the same, but if you want bespoke NPC types, you can change this.

<figure><img src="/files/peGBrOJTBEjO2UGX9FvC" alt=""><figcaption></figcaption></figure>

#### Advanced: Configuring NPCs to run differently on Worker Clients or the Server

The hope is that in most cases this won't be required. However, there are some quirks to note when spawning your NPCs on the server or a worker client:

* If the NPC is spawned on the server, there will be no "authoritative client". The pawn will be controlled by the server, which will have authority over its "client authoritative" properties.
* This means that `MorpheusActor::GetAnyLocalConnectionsHaveClientAuthority` branches will run fine on either machine (if it's as worker-controlled, this will be true only for the authoritative worker client, if server-controlled, this will be true on the server)
* However, any logic that explicitly checks whether it is running on the server or client will behave differently. i.e. if you check "is on client" to restrict certain behavior that you want running on the authoritative machine, then the server won't run it, even if it is the machine that's meant to be controlling your NPCs.
* Similarly, the `MorpheusActor::SwitchMorpheusAuthority` node will return `Server` on the server, even if it also has client authority. So this node may cause issues if you want logic that is run on the authoritative machine, server included.

If your NPCs depend on logic that can't be structured in a way where it can run the same with either client or worker control, you can configure your NPC spawners accordingly:

* `NPCClass` is the class used by default for NPCs spawned either on the server or worker clients.
* `OverrideServerClass` - if this is non-null, then any NPCs spawned on the server will use this class instead. You can use this if you want to give them alternative logic to worker NPCs.
* Is `SpawnOnServer` is false, then the spawner will not attempt to spawn NPCs on the server. (You can use this to e.g. only spawn NPCs on the workers, requiring workers to be available)
* Is `SpawnOnWorkerClients` is false, then the spawner will not attempt to spawn NPCs on worker clients. (You can use this to e.g. only spawn NPCs on the server)

<figure><img src="/files/p1Jq7aKDV8dqXwtJ4i2O" alt=""><figcaption></figcaption></figure>

### Requesting NPCs

The logic that the NPC Spawners call automatically, is accessible to be called elsewhere too. If you get the `BPM_ODK_NPCSingleton`, you can request that NPCs be spawned.

* `Server_SpawnNPCs` is a Server RPC request to spawn NPCs - it can be called from the server, or any client.

  <figure><img src="/files/jEBBozzUvqxpJhTEwuVb" alt=""><figcaption></figcaption></figure>

  * If you are on the server, you can also call `AttemptSpawnNPCs` instead, which returns `Success` and `NumSpawned`, allowing you to more tightly handle what happens if there was not sufficient capacity to spawn all the desired NPCs.

    <figure><img src="/files/zW1zuA41Coxj6UTwAphf" alt=""><figcaption></figcaption></figure>
  * `NumNPCs` is the number of NPCs of the provided type you want to spawn.
  * `RequireFullCapacity` controls whether to allow spawning a fraction of the NPCs, if there is not capacity for all of them, or whether to fail the request, to try again later. (e.g. if there is only capacity for 6 NPCs, but you want to spawn 10, should it spawn 6 now, or wait until a Worker Client connects, and it can spawn all 10?)
  * `NPCClass` is the Morpheus Actor class of the NPC you want to spawn. If you want different types of NPCs, you can change this freely.
  * `SpawnParameters` contains two fields:
    * `NPC Behavior`: The Behavior Tree that your spawned NPCs will run (see [#npc-behaviors](#npc-behaviors "mention"))
    * `SpawnPointProvider`: Generally pass in the caller. Needs to implement the `BPI_ODK_NPCSpawnPointProvider` interface
      * The NPC Singleton uses this to determine where to spawn the NPCs, calling `GetNPCSpawnPoint`

        <figure><img src="/files/HhCqAwih6OrRH2noeCo1" alt=""><figcaption><p>A basic <code>GetNPCSpawnPoint</code> implementation, used by the <code>BP_ODK_NPCSpawner_Radius</code> - spawn NPCs on the navmesh, in a radius around the spawn point actor.</p></figcaption></figure>
      * The Spawn Parameters are replicated to the Morpheus Actor, so can be used to provide additional information to the spawned NPCs on what to do. (e.g. if you configured a spawner where you wanted every NPC spawned from that spawner to wear a hat, you could make the NPCs check their spawn point provider to determine what hat to wear)

## NPC Behaviors

Each NPC Spawner has an associated `NPCBehavior` that any NPCs spawned from it will run. This is a Behavior Tree, that will control what the NPCs will do. This can be freely configured, or swapped out, if you want your NPCs to perform different behavior.

<figure><img src="/files/3zKQGsKeT1XT4Oa3gDv1" alt=""><figcaption><p>An example behavior tree: <code>BT_BotWander</code> - the NPCs will pick random locations nearby to them, and move to them. Causes the NPCs to wander randomly within the level, assuming navigation is in place.</p></figcaption></figure>

### Stations

For some more complex behavior, we have the "NPC Stations" system: the NPCs will move between "stations" placed in the world, and perform actions according to the station (e.g. performing an emote reaction to a point of interest).

For more details, see [NPC Stations](/odk-docs/odk-plugin/npcs/npc-stations)


# NPC Stations

{% hint style="warning" %}
This feature will only be available from ODK v10 onwards.

It is an experimental system, and is likely to change over the course of subsequent updates
{% endhint %}

{% hint style="info" %}
NOTE: Uses Unreal's Smart Objects system. For more information on this, see: <https://dev.epicgames.com/documentation/unreal-engine/smart-objects-in-unreal-engine---overview?application_version=5.5>
{% endhint %}

Using the [NPCs](/odk-docs/odk-plugin/npcs)system, we have a specific type of spawner: `BP_ODK_NPCSpawner_StationUser`.

NPCs spawned from this spawner run the `BT_ODK_UseStations` behavior: They look around for available `BP_ODK_NPCStation` actors, move to them, and perform the associated action.

## Configuring your station spawner

Each station spawner is a basic NPC spawner, with a few additions:

* `SpecificStations`: If entries are provided in this list, NPCs spawned from this spawner will only consider the stations in this list as viable stations to navigate to.

  <figure><img src="/files/TL7KBa1d1yDbfWVQf4rd" alt=""><figcaption></figcaption></figure>
* `StationSearchRange`: If there are no specific stations, stations will be found using this. Any stations within the range will be viable stations to navigate to.

  <figure><img src="/files/q6N1sp2FGKeUWpKpNdD8" alt=""><figcaption></figcaption></figure>

### Advanced: Station filter

<figure><img src="/files/5jmU3kyakabCOYTBpZM2" alt=""><figcaption><p>An example station filter: The stations' NPCs will be considered <code>TypeA</code>, and search only for stations that are <code>Restricted</code> or <code>Restricted2</code></p></figcaption></figure>

When searching for smart object slots, using either of the above approaches, it is possible to add `SmartObjectRequestFilter` details to it. With this you can e.g.

* Provide `UserTags` to represent NPCs in this station. In your stations' Smart Object Definitions, you can add a `UserTagFilter` to only accept NPCs that have the required tags.
* Provide `ActivityRequirements`. These are effectively the inverse of the above - the NPCs will only accept stations/slots that have the required `ActivityTags`

  <figure><img src="/files/FJvmwrVkDCZU5T4dOfzk" alt=""><figcaption><p>Example details within a Smart Object Definition. This smart object is a <code>Restricted</code> type, and only accepts users (NPCs from a station) that has <code>TypeB</code> as one of its <code>UserTags</code>.</p></figcaption></figure>

## Configuring your stations

Each station extends `BP_ODK_NPCStation`, and can be placed in the world. This will be the target that NPCs will move to.

* `SmartObjectDefinition` controls the behavior that NPCs will perform upon reaching the target, along with other properties. By default we have the `SOD_ODK_NPCStation_Blank` definition, which means the NPC will not do anything upon reaching the target - they will immediately move on to the next target (for randomly walking)
* `RequiresMatchingRotation` is a tickbox that can be used to control whether NPCs need to match the rotation of the station's slot before running the action, or not

  <figure><img src="/files/g02zNOz6mWUlfwao9VhJ" alt=""><figcaption></figcaption></figure>
* `MoveToSpeed` lets you control whether NPCs will walk, jog or run to the target.

(If you use a smart object definition with multiple slots, each slot is a viable "target" for NPCs to move to and interact with)

<figure><img src="/files/7FCfEXhGPvGnpYuFNSp4" alt=""><figcaption></figcaption></figure>

### Smart Object Behaviors

Each station is a Smart Object, with a Smart Object Definition. The behavior definition within a smart object definition controls what behavior runs when interacting with the object.

The most common usage is to add an entry to your `DefaultBehaviorDefinitions`: Add a `GameplayBehaviorSmartObjectBehaviorDefinition`, and a `GameplayBehaviorConfig` within that. You can make and provide your own `BehaviorClass`, where you can add your custom logic.

* As an example, see `GameplayBehavior_ODK_PlayMontage`: This handles playing a montage, getting some additional relevant information from the station.
  * The event to implement is `Event OnTriggeredCharacter`. When finished, pass or fail, call `EndBehavior` to move on.

{% hint style="info" %}
NOTE: One of the existing gameplay behavior config classes is `GameplayBehaviorConfigBehaviorTree` (instead of a regular `GameplayBehaviorConfig`). This has not been verified to work with our current setup, so is not recommended.
{% endhint %}

<figure><img src="/files/GWDjgucuyA7XcJGIEHbX" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
NOTE: Each Smart Object Definition must have at least one slot. This can be the blank "None" slot created if you click the `+` button:

<img src="/files/L1imIndjuHgqhUBKrrJk" alt="" data-size="original">
{% endhint %}

### Montage Stations

These are a specialised form of station, pre-made for convenience. They are set to use the `SOD_ODK_NPCStation_PlayMontage` smart object definition, which will play the configured `MontageName` upon interacting with the station.

(If a `MaxPlayDuration` is provided, then the montage will only play for at most that duration. This avoids NPCs looping forever if they play a looping montage)

<figure><img src="/files/0RMU8o9yruf0leq26zJt" alt=""><figcaption></figcaption></figure>


# Respawn and Teleportation

The and BPMC\_ODK\_TeleportComponent can respectivly be used to respawn or teleport the parent morpheus actor.

To respawn, call "Respawn" on  the BPMC\_ODK\_RespawnComponent. You must specify:\
\- TargetExecutor - An executor object that will be used by the respawn component to work out the location and rotation to respawn the owning morpheus actor to.\
\- TeleportType - This defines what effects will play before and after the player respawns.\
\- NoCheck - Whether we should check for collisions or not at that respawn location.\
\- RotateController - Whether to also rotate the player controller as well as the morpheus actor to the respawn rotation.

<figure><img src="/files/jr3EmlbAtcrf44IMWCnt" alt=""><figcaption></figcaption></figure>

To teleport, call either "Teleport", "TeleportToWorldTarget", "TeleportToActorTarget". We will examine the "Teleport" function. You must specify:

\- TeleportType - This defines what effects will play before and after the player teleports.\
\- TeleportTarget - An executor object that will be used by the teleport component to work out the location and rotation to teleport the owning morpheus actor to.\
\- NoCheck - Whether we should check for collisions or not at that respawn location.\
\- RotateController - Whether to also rotate the player controller as well as the morpheus actor to the respawn rotation.\
\- OnTeleport - A delegate that will be fired when the play

<figure><img src="/files/cz89irKABnj8cmPwsLzj" alt=""><figcaption></figcaption></figure>


# Player Profile

Who's who?

All player profile information about users is available within the `BPMC_ODK_PlayerProfileComponent`. This is a morpheus replicated component, and available on every player's morpheus actor. However some information is only replicated on Foreground Actors, so information for non-authoritative players will only be available dependent on the relevancy of the non-auth player to the calling player.

{% hint style="info" %}
Note: When retrieving data from the player profile, always make sure the `IsPlayerProfileLoaded` function has completed first, to ensure your data is relevant. You can also bind to the `OnPlayerProfileLoaded` event on the same component to be informed when a profile's data has been loaded.
{% endhint %}

#### Player name (foreground replicated)

A player's name is available via the `GetPlayerName` function on the component, and you can listen for updates via the `OnPlayerNameUpdated` event

#### Player profile url (foreground replicated)

A player's profile url is available via the `GetProfilePictureUrl` function on the component, and you can listen for updates via the `OnProfilePictureUrlUpdated` event.


# ODK Templates

To provide starting points, and examples for how to build functionality, the ODK is shipped with a handful of template projects.

<figure><img src="/files/BTOCeZ0xYg0IBLfiotr0" alt=""><figcaption><p>The Templates page within the ODK launcher</p></figcaption></figure>

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 [ODK plugin](/odk-docs/odk-plugin) content.

ODK Combat Template: This template provides examples of how you can implement a high-fidelity shooter within the ODK.


# Boneyard Template

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.

<figure><img src="/files/aq9AZp6UMfiLS0TrYqLS" alt=""><figcaption></figcaption></figure>

All content within this space should be used as an example for how we have achieved aspects of functionality.

### BPM\_ODK\_Example\_PlayerCharacter

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.

### BP\_ODK\_Example\_PlayerCharacter

This Blueprint extends from `BP_ODK_PlayerCharacterBase` and should be used to add your own character logic.

### BP\_ODK\_Example\_PlayerController

This Blueprint allows easy extension of the [World Builder](/odk-docs/readme/technical-overview) player controller. It should be used when you wish to inherit M2 base functionality while also adding your own logic.


# Coins

Coins in the Boneyard are examples of how you can implement collectables on chain, these pickups grant players tokens to their wallets.

<figure><img src="/files/1KEC8TrDv5eqmAgpdxkI" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/qIeVl87cs5gtvMiub8ey" alt=""><figcaption></figcaption></figure>


# Arcade Machine

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](https://toptrader.xyz/) which is done by using the blueprint `BP_Interactable_OpenURL` and calling `OpenWebBrowser` with the desired URL on the `BP_WebBrowserWorldService`

<figure><img src="/files/RO8KYtE3hkSqHUNRIFlf" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/O4PvXq77YT85u4tJso0z" alt=""><figcaption><p>Toptrader.xyz being opened via the Web Browser</p></figcaption></figure>


# Token Gating

Our guide to example the Token Gating system can be found [here](/odk-docs/odk-plugin/token-gating):\
\
We have three examples of Token Gating in the Boneyard Experience.

<figure><img src="/files/eYStFCyFEaZtSRGh0ZR3" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/hBzOpk5EwWSloNW39JQd" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/ES0YxgBLjcwayTVh0Emw" alt=""><figcaption></figcaption></figure>

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.


# Combat Template


# Teams

## Feature Overview

The Meebit template comes with an example implementation of teams. Players can join different teams to shoot players on different teams.

## Technical Overview

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.

## Tutorials:

### Adding a 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!

<figure><img src="/files/w1QldFxQFahlmMHL9kp3" alt=""><figcaption><p><code>E_Team</code> enumeration</p></figcaption></figure>

<figure><img src="/files/517lFXumItDVgUsYP55S" alt=""><figcaption><p><code>DT_TeamData</code> data table</p></figcaption></figure>

<figure><img src="/files/lCXllnWrO8K0Gh3cVBaT" alt=""><figcaption><p><code>GetTeamIndex</code> and <code>GetTeamName</code> helper functions</p></figcaption></figure>


# Scoreboard

## Feature Overview

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.

## Technical Overview

All functionality can be found in `WBP_Scoreboard`.

<figure><img src="/files/k3PtvL1whHMTfULhvjHq" alt=""><figcaption><p>Scoreboard UI</p></figcaption></figure>


# Team Select

## Feature Overview

The team select UI allow players to change team. Use the "N" key to open the UI.

## Technical Overview

All functionality can be found in `WBP_TeamSelect`.

<figure><img src="/files/WpHIeWYUe3sXiZShXzcw" alt=""><figcaption><p>Team Select UI</p></figcaption></figure>


# Respawn Points

## Feature Overview

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.

## Technical Overview

`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.

<figure><img src="/files/XtW81Mb3Ci04SbQyvjHB" alt=""><figcaption><p>Caching player start volumes in <code>BPM_ODK_ExampleCombatCharacter</code></p></figcaption></figure>

<figure><img src="/files/a9VWw1NDiAENIeQumrch" alt=""><figcaption><p>Respawn logic in <code>BPM_ODK_ExampleCombatCharacter</code></p></figcaption></figure>


# Role Promotion

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".

<figure><img src="/files/lhdO68szUEKyeS1llftA" alt=""><figcaption></figcaption></figure>


# Otherside Vibe Maker

This is a page that documents the workflows and functionality that are possible with MML within Otherside. Updated as of March 2026.

These docs will evolve and get updated as MML functionality comes online, this guide contains the following sub pages

* Create your first MML - This page walks you through the creation of your first MML object and goes from LLM command through to placing it in game
* Supported MML Types - This section highlights the differences between Static and Dynamic MML's
* Supported Tags - This identifies what tags of the MML API are supported within Unreal.


# Create your first MML

What you need to start creating

1. A LLM enabled IDE or prompt window e.g Cursor.
2. Access to the MML Editor: <https://mmleditor.com/explore>
3. Access to <https://www.otherside.xyz/mmls>

Useful Documentation

1. <https://mml.io/docs> - Msquareds documentation on MML (Metaverse Markup Language)

How to create your first MML object

* Create an account at: [https://mmleditor.com/](https://mmleditor.com/explore)

<figure><img src="/files/ECef4B0P9Oz1mX3DJCTs" alt=""><figcaption></figcaption></figure>

* Create a New Project on the MML Editor Homepage

<figure><img src="/files/v67l6HPobgjk4CgPffI0" alt=""><figcaption></figcaption></figure>

* The MML Editor will open for your new MML document, I would give the MML a new name by going Info ---> Edit Name in this instance I will call it "My First MML"

<figure><img src="/files/IsMjUwXJj9iOuUlj0zhj" alt=""><figcaption></figcaption></figure>

* If you are using Cursor, I would create a new Folder on your machine called "MML Creations" and then open that folder with Cursor. Start a new agent with your chosen LLM model, and you can start prompting, for this example I am going to build a Rocket with the following prompt "*Create a rocket ship that takes off in Metaverse Markup Language in a new HTML file"* You can be more descriptive with your creation depending on what you are wanting to create.

<figure><img src="/files/cdCQbCi8Xu3ocor6HJi6" alt=""><figcaption></figcaption></figure>

* Once your chosen agent has created the first pass you can copy and paste it into the MML editor in the CODE viewport to see how its turned out

<figure><img src="/files/oHIzTSbRm3U8yF46oS8Z" alt=""><figcaption></figcaption></figure>

* When you are happy with your MML creation click "Static Version" in the top right, then Publish and then copy the URL.
  * If you want a Dynamic MML then go to Share and copy the web socket link, your MML must be running on your local browser within the MML editor for it to work in world as your local machine acts as a server. At a later date we may add a MML hosting service.

<figure><img src="/files/PavrK48DMVOskv1iiB2P" alt=""><figcaption></figcaption></figure>

* Next head to <https://www.otherside.xyz/mmls> and add your URL + name.

<figure><img src="/files/sihV2LXjRRSnC6NqtRoB" alt=""><figcaption></figcaption></figure>

* Once you have clicked Add MML it will be stored against your profile, and will appear in the drop down. If you cannot see the drop down in world you will need to be added to the MML Director group if its not available in the roles dropdown in settings


# Supported MML Types

This page will outline that types of MML are supported

* Static MMLS Static MML's are fully supported and are the most simple form the MML type.
  * Static MML's can't contain javascript code and therefore do not support interaction or OnClick events.
  * Static MML's utlise all supported Tags
  * Static MML's cannot be edited live, and when a change is made a new link will need to be published from the MML Editor.
* Dynamic MML's are partially supported, these MML's can contain javascript and can be edited live, which means you don't need to publish a new link for every change you make. The state and MML code will syncronise with every instance of the object for example if you have a jukebox and the song can be changed, the song will update in every world where this MML is present.
  * Interaction/Player click events are currently <mark style="color:$danger;">**NOT**</mark> supported in Unreal
  * You MUST keep the MML running within the MML Editor in your browser as this acts as the server
    * A MML hosting service may come online at a later date to circumvent this restriction<br>


# Supported Tags

This page outlines what MML tags are supported with the Unreal Plugin. We will add further support over time and update this page as further tags come online.

Info accurate as of 3 Mar 2026.

<table><thead><tr><th width="231.609375" align="center" valign="middle">MML Tag</th><th width="283.24609375" align="center">Supported In Unreal (Yes/No)</th></tr></thead><tbody><tr><td align="center" valign="middle"><code>&#x3C;m-group></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><p><code>&#x3C;m-cube></code></p><p><code>&#x3C;m-sphere></code></p><p><code>&#x3C;m-cylinder></code></p></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-light></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-plane></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-model></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-character></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-audio></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-image></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-video></code></td><td align="center"><mark style="color:$success;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-label></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-position-probe></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-prompt></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-link></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-interaction></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-chat-probe></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-attr-anim></code></td><td align="center"><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td align="center" valign="middle"><code>&#x3C;m-attr-lerp></code></td><td align="center"><mark style="color:$danger;"><strong>No</strong></mark></td></tr></tbody></table>


# Otherside Agentic API

This guide is for integrators who want to call Otherside Agent APIs from bots, backend services, or automation scripts.

## Base URL

* `https://otherside.xyz`

All endpoints below are relative to this base URL.

## What These APIs Do

* `GET /api/agents/privy-id`: map a wallet address to a Privy ID segment.
* `GET /api/agents/user-data`: read a user's location payload.
* `GET /api/agents/chat`: read text chat for a world.

## Pricing

* `GET /api/agents/privy-id`: `0.10` USDC.e
* `GET /api/agents/user-data`: `0.001` USDC.e
* `GET /api/agents/chat`: `0.001` USDC.e

## Payment Model (x402)

#### <mark style="color:$success;">NOTE: APIs are currently open and free during the developer preview period. No x402 handshake is required.</mark>

These endpoints use x402 challenge-response:

{% stepper %}
{% step %}
Call the endpoint normally (no payment header).
{% endstep %}

{% step %}
Receive `402` with `PAYMENT-REQUIRED`.
{% endstep %}

{% step %}
Build/sign payment payload from challenge requirements.
{% endstep %}

{% step %}
Retry same request with `PAYMENT-SIGNATURE` header.
{% endstep %}

{% step %}
On success, read `PAYMENT-RESPONSE` header for settlement details.
{% endstep %}
{% endstepper %}

## Chat World Targeting

For chat endpoints, callers can provide either:

* `worldId` (direct world identifier), or
* `world` (friendly alias label like `SWAMP` or `NEXUS`)

The API resolves world aliases server-side.

## Suggested Integration Flow

For "wallet to world chat" flows, use:

{% stepper %}
{% step %}
`GET /api/agents/privy-id?wallet=...`
{% endstep %}

{% step %}
`GET /api/agents/user-data?userId=...`
{% endstep %}

{% step %}
`GET /api/agents/chat`
{% endstep %}
{% endstepper %}


# Steps (In Depth)

{% stepper %}
{% step %}

### Resolve wallet to Privy ID

Request:

```bash
curl -i "https://otherside.xyz/api/agents/privy-id?wallet=<EVM_WALLET>"
```

Expected first response:

* `402 Payment Required`
* `PAYMENT-REQUIRED` header

Retry with `PAYMENT-SIGNATURE` after building/signing payload.

Success shape:

```json
{
  "wallet": "0x...",
  "privyId": "clynngec201kjwo4028etve1c"
}
```

{% endstep %}

{% step %}

### Fetch user location data

Request:

```bash
curl -i "https://otherside.xyz/api/agents/user-data?userId=did:privy:<PRIVY_ID>"
```

Expected first response:

* `402 Payment Required`
* `PAYMENT-REQUIRED` header

Retry with signed `PAYMENT-SIGNATURE`.

Success shape:

```json
{
  "userId": "did:privy:...",
  "data": {
    "worldId": "my-world",
    "position": { "x": 0, "y": 0, "z": 0 }
  }
}
```

If user has no location record:

```json
{
  "userId": "did:privy:...",
  "data": null,
  "message": "No user data available"
}
```

{% endstep %}

{% step %}

### Read chat

Use `SWAMP` or `NEXUS` as the world param.

```bash
curl -i "https://otherside.xyz/api/agents/chat?world=SWAMP&page=1&limit=20"
```

This endpoint is paid and follows the same 402 -> sign -> retry pattern.
{% endstep %}

{% step %}

### Post bot chat message

```bash
curl -i -X POST "https://otherside.xyz/api/agents/chat" \
  -H "Content-Type: application/json" \
  -d '{"username":"bot-user","message":"hello world","world":"NEXUS"}'
```

This endpoint is paid and also requires x402 challenge handling.
{% endstep %}
{% endstepper %}

### Error Handling Checklist

* `400`: invalid request input.
* `402`: expected when payment is missing/invalid.
* `404`: target record missing (for example wallet not found in Privy route).
* `502` / `503`: upstream/configuration issues; retry with backoff.

Always log:

* `PAYMENT-REQUIRED`
* `PAYMENT-RESPONSE`


# Example Code

## JavaScript: Generic paid endpoint helper

```ts
async function callPaidEndpoint(url: string, init?: RequestInit) {
  const first = await fetch(url, init)
  if (first.status !== 402) return first

  const challengeHeader = first.headers.get("PAYMENT-REQUIRED")
  if (!challengeHeader) {
    throw new Error("Missing PAYMENT-REQUIRED header on 402 response")
  }

  const paymentRequired = JSON.parse(
    Buffer.from(challengeHeader, "base64").toString("utf8"),
  )

  // Build/sign this payload with your wallet stack (EIP-3009 transferWithAuthorization)
  const paymentPayload = await buildAndSignX402Payload(paymentRequired)
  const paymentSignature = Buffer.from(
    JSON.stringify(paymentPayload),
    "utf8",
  ).toString("base64")

  return fetch(url, {
    ...init,
    headers: {
      ...(init?.headers ?? {}),
      "PAYMENT-SIGNATURE": paymentSignature,
    },
  })
}
```

## JavaScript: wallet -> privy-id -> user-data -> chat

```ts
const BASE_URL = "https://otherside.xyz"

export async function fetchUserChatFromWallet(wallet: string) {
  // 1) wallet -> privyId
  const privyResp = await callPaidEndpoint(
    `${BASE_URL}/api/agents/privy-id?wallet=${encodeURIComponent(wallet)}`,
  )
  if (!privyResp.ok) throw new Error(`privy-id failed: ${privyResp.status}`)
  const privyJson = await privyResp.json()
  const privyId = String(privyJson.privyId)

  // 2) privyId -> user-data
  const userId = `did:privy:${privyId}`
  const userDataResp = await callPaidEndpoint(
    `${BASE_URL}/api/agents/user-data?userId=${encodeURIComponent(userId)}`,
  )
  if (!userDataResp.ok) {
    throw new Error(`user-data failed: ${userDataResp.status}`)
  }
  const userDataJson = await userDataResp.json()
  const worldId = userDataJson?.data?.worldId
  if (!worldId) throw new Error("No worldId found in user data")

  // 3) worldId -> chat
  const chatResp = await callPaidEndpoint(
    `${BASE_URL}/api/agents/chat?worldId=${encodeURIComponent(worldId)}&page=1&limit=20`,
  )
  if (!chatResp.ok) throw new Error(`chat failed: ${chatResp.status}`)
  return chatResp.json()
}
```

## cURL: chat by world alias

```bash
curl -i "https://otherside.xyz/api/agents/chat?world=SWAMP&page=1&limit=20"
```


# Continuous Development

Updates on Continuous Otherside Development

### 2026.05.20

**New Otherside Launcher**\
\
**PC Users:** will automatically be updated to the Otherside Launcher when launching Otherside.\
\
**Mac Users:**\
**Step 1:** Go to your applications folder\
**Step 2:** Find M2 Launcher, right click and choose Move to Trash/Bin\
**Step 3:** Visit [Otherside.xyz](https://www.otherside.xyz/)\
**Step 3:** Click "DONT HAVE THE LAUNCHER?" to download the Otherside Launcher\
**Step 4:** Install the Otherside Launcher

<div align="left"><figure><img src="/files/zkDYKsvbQVH8dLi8NYAT" alt=""><figcaption></figcaption></figure></div>

<figure><img src="/files/L0pICCFhnwWLGXdVFnUt" alt=""><figcaption></figcaption></figure>

### 2026.04.01

**New Features**

* Level cap increased from 40 to 60
* Players can now customize World and Bubble emote wheel thought Profile overlay
  * Profile --> Avatar --> Emotes

<figure><img src="/files/LZbacCvTCOY3vh849r2F" alt=""><figcaption></figcaption></figure>

**Content Updates**

* Holiday event activation added (will begin 04/03/26)
* New Swamp Daily Tasks
* 5 New Badges Added
* 3 New Challenges Added
  * Bone Wurm Challenge moved to Bone Biome

**Bug Fixes**

* **5000 Series driver issue resolved**
* Fixed a crash when opening the Profile overlay
* Allowed players to set their own camera zoom level
* Chaos collectibles can now be muted via the volume slider
* Fixed an issue with the update username function timing out after 10 seconds
* Improved the time between taking a photo with Kodacam and it appearing in your gallery

### 2026.03.19

**New Features**

* Mega Koda room now accessible to all Kodas and Mega Kodas
* New Challenge - find your way into the Mega Koda room
* Top Challenge times are now displayed on the video wall in the Pulse District
* Challenge Daily Task entry points now appear on the HUD and compass
* Chaos Particle minigame with associated Daily Tasks

\
**Content Updates**

* Reset of Challenge leaderboard times
* New Daily Tasks - Help clean up the Chaos energy in the Temple
* New Badges - Earn new badges with your Koda Cam
* Updated Challenge HUD to improve chest tracking

\
**Bug Fixes**

* Accuracy of player position co-ordinates on the Map overlay increased
* Fixed a crash when travelling between the FTUE and Nexus
* Right click no longer unlocks the mouse cursor, functionality has been moved to the “U” key

### 2026.02.05

**New Features**

* New Multiplayer challenge
* New Single Player Challenge
* Updated Player HUD to show keybinds on demand
* 10 New Daily Tasks
* 7 New Badges
* Activities & content for upcoming seasonal events

**Content Updates**

* Reset of Challenge leaderboard times
* Player position co-ordinates added to the Map overlay
* Updated Leaderboard layout
* Improved Challenge UI
* “Amateur Geologist” Badge can now be earned by visiting either of the Crystal Cave locations
* Added Daily Tasks that include Co-op Emotes

**Bug Fixes**

* Fixed an XP exploit related to World travel
* Allowed Mutant Apes to leave Challenges via Entry Portals
* Fixed a character scaling issue when bouncing on Sludge
* Fixed a number of floating rocks in the Nexus that were spinning too quickly
* Updated audio attenuation on spinning lasers
* Stopped the “Cosmic Wanderer” Badge from being truncated when awarded
* Portal VFX no longer get culled at the edges of the screen

### 2026.01.22

**New Features**

* “Action in Location” Daily Tasks
* 10 New Daily Tasks
* 9 New Badges

**Content Updates**

* Gator contents added to the Swamp
* Visual updates to the Opensea Ship
* Updated the keyboard layout in the Help menu to reflect latest keybindings
* Adjusted Chest collection volume to avoid rare cases where the chest could be opened while the forcefield was still in place
* Grounded floating rocks near the Nexus spawn point
* Updated the wording of the “Daily Challenge” daily task to make the objective clearer
* Tweaked the layout of the Koda Cam gallery so that photos no longer overlap the UI
* Updated Koda Cam UI to only display hotkey buttons that have applicable functionality
* Bubbles Hosts no longer receive speaker request notifications if their Bubble has ended
* Bubbles Hosts can now search for their own Bubbles

**Bug Fixes**

* Fixed a rare GPU crash on native client
* Improved window focus logic to resolve an issue where some controllers could control the player avatar and desktop mouse cursor simultaneously
* Rescaled wormhole VFX on Falling Path & Void Dive Challenges
* Updated “The Finer Things” Daily Task to avoid the user’s photo occasionally failing to register
* Removed some conditions where the user could be awarded a Badge they had not earned when logging in
* Fixed an issue where opening the Koda Cam while creating a Bubble stopped the user from closing the Koda Cam afterward
* Fixed an issue on “Leap of Faith” where the chest occasionally failed to open after collecting all Gems
* Improved performance when switching between tabs in the Overlay Menu
* Fixed flickering sand in the Crystal Caverns
* Improved closing of the Emote Wheel when using a controller
* Fixed an edge case where some users were unable to open the Koda Cam using the G key
* Fixed a freeze that could occur when playing the Tutorial on Mac

### 2025.12.18

**Bug Fixes**

* Fixed KodaCam inputs not working on controller (take pic, gallery etc)
* Fixed KodaCam prompt showing in Bubbles
* Fixed calling KodaCam and Bubbles simultaneously causing a soft lock.
* Kodacam can be enabled/disabled with controller
* Fixed HUD keybind hints overlapping chat
* Challenge 25 chest unlock can no longer be blocked
* Tilt controls in Kodacam now work on controller
* Disabled Kodacam in Bubbles
* Disabled glide & camera rotation when in Kodacam selfie mod
* Fixed Infinite glide exploit when using Koda cam
* Voyager NPC Santa Hats added

### 2025.12.11

**New Features**

* KodaCam
* 3 New Single Player Challenges
* 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

### 2025.09.12

**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

### 2025.09.05

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

### 2025.09.03

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


# Morpheus Platform Documentation

Welcome to the Morpheus Platform documentation!

## What is Morpheus Platform?

Morpheus Platform is a cutting-edge development and hosting environment that enables the creation of large-scale, high-fidelity virtual worlds and events using Unreal Engine 5. It is designed to support tens of thousands of concurrent users without world sharding, it offers next-gen graphics, spatial audio, and cross-device compatibility.

ODK Creators can rapidly deploy updates, manage user access, and host secure, scalable experiences with built-in tools for event management. Morpheus Platform empowers creators to build immersive metaverse experiences with full control and seamless integration into existing systems.

## Next Steps

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Unreal development</strong></td><td>Download editor &#x26; start creating</td><td></td><td><a href="/pages/lsDzWbPCoSnWWhJ7lbBi">/pages/lsDzWbPCoSnWWhJ7lbBi</a></td><td><a href="/files/Wf2C4cWDTYAFxpaYKZoU">/files/Wf2C4cWDTYAFxpaYKZoU</a></td></tr><tr><td><strong>Production</strong></td><td>Start worlds, create events, &#x26; manage player access</td><td></td><td><a href="/pages/HxkV8v4qn7hA89okSBW1">/pages/HxkV8v4qn7hA89okSBW1</a></td><td><a href="/files/7rzUgjS9IBriBmlpYeVL">/files/7rzUgjS9IBriBmlpYeVL</a></td></tr><tr><td><strong>Admins</strong></td><td>Manage access, see usage, and change settings</td><td></td><td><a href="/pages/VsgWbWser4FPuQbJ9M6J">/pages/VsgWbWser4FPuQbJ9M6J</a></td><td><a href="/files/rTakhmiS1waoyIzSvEpi">/files/rTakhmiS1waoyIzSvEpi</a></td></tr></tbody></table>


# What is Morpheus Platform?

Morpheus Platform is a cutting-edge development and hosting environment that enables the creation of large-scale, high-fidelity virtual worlds and events using Unreal Engine 5 within Otherside. It is designed to support tens of thousands of concurrent users without world sharding, it offers next-gen graphics, spatial audio, and cross-device compatibility.

## Next Steps

* [Glossary](/platform-documentation/morpheus-platform/glossary)
* [Interoperability](/platform-documentation/morpheus-platform/interoperability)
* [Support](/platform-documentation/morpheus-platform/help-and-support)
* [EULA](/platform-documentation/morpheus-platform/eula)


# Glossary

### Metaverse

Metaverses in Otherside are creatively autonomous yet interoperable. They are groups of virtual worlds that exist as part of the broader interoperable M² network.

The metaverse is M²'s unit of organisation. Every M² virtual world is part of a particular metaverse, and each metaverse has separate access control, billing, etc.

Each metaverse has its own URL based on its codename:

> **\<codename>.m2worlds.io**

### Dashboard

The dashboard is the entry point to both metaverses and tools. It is how developers and other metaverse operators interact with the M² network. From here:

* Admins can manage access across the metaverse, handle billing, and additional configuration
* Developers can download the editor, and launch virtual worlds that are made from it
* Event runners can run events in virtual worlds using content uploaded by developers

You can find your metaverse's dashboard based on your `organization id`:

> **https\://{organization-id}.m2worlds.io/dashboard**

**For example:**

* Construct's dashboard is [https://construct.m2worlds.io/dashboard](#m)

### Worlds

Worlds are single instanced virtual spaces, individually capable of up to \~10ks CCUs. Worlds are launched by clicking `Launch` in the header and involve selecting a map (from a mod), runtime, size (how many players can it handle), and more.

<figure><img src="/files/OHxRq8uGpyVEuLiXBLsB" alt=""><figcaption><p>Configure a world to launch</p></figcaption></figure>

Standard worlds will be deleted after the duration of the world has ended. If this is not your desired outcome, you can set up persistent or always on worlds.

[Persistent worlds](/platform-documentation/creation/worlds/persistent-worlds) - Worlds that always exist, but are not always on. By setting up persistent worlds for events, the creator will not have to remake or reconfigure access groups, live config, links, events, and any other world state already configured.

[Always on worlds](/platform-documentation/creation/worlds/always-on-world) - An extension of persistent worlds that are set up without a duration end time.

### Mods

Mods are developer-uploaded Unreal projects that can be used to start worlds. Each developer gets their own mod which is automatically created for them the first time they click upload in the editor. All future uploads will be part of the same mod. When starting a world, you always select a mod (and a map within that mod) to run. By default, only the mod creator (and admins) can use a given mod to launch worlds.

Mods can be viewed in the `Mods` tab of the dashboard. Below, you can see the `ally's game` mod, and a history of the 14 different versions of it that have been uploaded.

<figure><img src="/files/pF7jm8L43G3CSzXowgLV" alt=""><figcaption><p>Mod list</p></figcaption></figure>

### Events

Events are a dedicated concept for engagement of users into a running world. You can create an event to advertise your metaverse events ahead of time using them as placeholders on your end user website with calendar links, YouTube trailers, and more. At show time, you can link the event to a running world to connect users into a world based on your content.

<figure><img src="/files/BAGlqcAzcDpdcwuhxeki" alt=""><figcaption></figcaption></figure>

### Projects

[Projects](/platform-documentation/admins/settings/projects) are isolated sections of a metaverse which let you separate groups of content, worlds, and people developing within your metaverse.

### Editor

The M² editor is an extended Unreal Editor including plugins for creating worlds with high-scale rendering/networking/audio, MML, interoperable objects, avatars, and tooling to upload to our content pipeline.

<figure><img src="/files/wGGVNv7StXZe9Ic6QISZ" alt=""><figcaption></figcaption></figure>


# Interoperability

Guidance around MSquared's network and interoperability mechanisms

<figure><img src="/files/zCE6Y4TJtYOcBxr3NnbL" alt=""><figcaption></figcaption></figure>

MSquared, besides being a platform for building high-density virtual world experiences, also features object interoperability as a first-class feature. This enables users to take their virtual possessions between many worlds with minimal work from you as a developer.

This interoperability is achieved through a combination of on-chain NFT records, an omnichain blockchain indexer, MML for interoperable object definition, and a mechanism for permissioned object storage and content moderation.

This guide explains in more detail how this mechanism works, and the current and in-development capabilities.

{% hint style="info" %}
NFTs don't necessarily need to be owned and custodied by users in a financially tradeable way - the same standard can be used for more traditional digital "entitlements" by using a mechanism called [Soulbound Tokens](https://www.coindesk.com/learn/what-are-soulbound-tokens-the-non-transferrable-nft-explained/)
{% endhint %}

### Interoperability Commitment

MSquared wants to help grow a network of interconnected experiences which the whole is greater than the sum of the parts - enabling free movement of users and their possessions between metaverses.

Part of the terms of using MSquared's platform is the commitment to this interoperability for at least user's appearance - their Characters, Clothing, Accessories and Emotes, given they pass your [moderation policy](/platform-documentation/creation/unreal-development/features-and-tutorials/communication/moderation).

### Types of Interoperable Object

Below are currently planned types of interoperable objects, and their current implementation status

<table data-full-width="false"><thead><tr><th>Type</th><th>Description</th><th width="91.5" data-type="checkbox">Status</th><th>Mechanism</th></tr></thead><tbody><tr><td>🖼️📷 Image</td><td>Typical picture-based NFTs - could be used as profile pictures or placing in world into photo frames etc</td><td>true</td><td>Reading the <code>image</code> field on the NFT metadata</td></tr><tr><td>💃🕺 Character</td><td>Humanoid characters that can be used as the player's avatar, or used for non-player characters</td><td>true</td><td>Reading the <code>mml</code> field on the NFT metadata linking to an MML <code>&#x3C;m-character></code> - see <a href="/pages/Low78BTPgHA9jCNvDTKk">MML Avatars</a></td></tr><tr><td>🪩🪑 Dynamic Object</td><td>Spawnable items that may be static or interactable - a chair, a jukebox, a quiz machine, etc</td><td>true</td><td>Reading the <code>mml</code> field for a <code>wss://</code> to a live MML object - see <a href="#types-of-interoperable-object">https://mml.io</a></td></tr><tr><td>👜🎩 Accessory</td><td>Static 3D models attached to particular part of a Character such as hats, glasses, backpacks, etc</td><td>false</td><td>Currently supported as part of a Character</td></tr><tr><td>👕👖 Clothing</td><td>Skeletal meshes layered on top of a base Character</td><td>false</td><td>Currently supported as part of a Character</td></tr><tr><td>🥳👏 Emotes</td><td>Short animations that can be played to express emotions or interact</td><td>false</td><td>Currently in exploration</td></tr></tbody></table>

### Interoperable Ownership

Ownership between different metaverses is established by associating one or many web3 wallets with a particular user.

MSquared supports users linking self-owned web3 wallets such as [MetaMask ](https://metamask.io/)out of the box. Enterprise customers can request more bespoke integrations if they have existing web3 identity systems.

<figure><img src="/files/nVrkWNJAwrMBk5TPJs5U" alt="" width="320"><figcaption><p>Wallet linking is a bundled feature of the reference web portal</p></figcaption></figure>

#### Delegation

Delegation is a method for NFTs (often high value ones) to be non-permanently granted in a read-only manner to another "hot" wallet which is used for more day-to-day interaction. MSquared supports both [https://warm.xyz/](https://warm.xyz) and <https://delegate.xyz/> (v1 and v2) mechanisms.

#### Ownership Logic

While delegation protocols technically support concurrent delegation of an NFT to multiple wallets, the following logic is in place to prevent scenarios where an NFT being used for access gating could be used to allow large numbers of people to share the same "ticket":

* For any wallet, it can only be linked to one user account at a time
* For any token, it can only be delegated to one wallet a time
  * If there are multiple delegations, the most recent delegation is prioritized
  * This is true across all supported delegation methods (e.g. a recent [warm.xyz](https://warm.xyz) delegation would take priority over a less recent [delegate.xyz](https://delegate.xyz) delegation)

#### User Collections

Successfully imported interoperable objects will be visible in the user's [Collection](/platform-documentation/creation/unreal-development/features-and-tutorials/user-collections#theinventorysystem-overview), which combines both "foreign" interoperable objects across multiple blockchains with "native" off-chain digital goods for your metaverse.

### Supported NFT Tokens & Metadata

We currently support ERC-721 and ERC-1155 tokens

{% hint style="info" %}
Delegation of ERC-1155 balances is not currently supported
{% endhint %}

The following fields within an ERC-721/ERC-1155 metadata JSON are respected.\\

| Field         | Usage                                                                                                         |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| `name`        | The canonical name of the object in a user’s collection                                                       |
| `description` | The additional descriptive text for a user, used in the collections frontend react components                 |
| `image`       | Used as a preview image for any collection item, and allows its usage as a profile picture                    |
| `mml`         | The URL to a static MML document containing an `<m-character>`. Allows the user to use it as a default avatar |
| ….            | Other fields are imported, and available for querying in Unreal and Web APIs                                  |

### Classification & Moderation

{% hint style="warning" %}
While the interoperable network is in Early Access, interoperable objects are currently restricted to those approved by MSquared directly, rather than being moderated - the wider moderated network will be launching later in 2025
{% endhint %}

Some types of interoperable objects may not be suitable for your particular experience. All objects within the interoperable network will be subject to a classification process, resulting in tags being applied to the object.

Metaverses will be able to define your interoperable object import policy based upon these tags.

<figure><img src="/files/I7q7o2xIwVoNGXG52911" alt=""><figcaption><p>All interoperable content is classified with tags, and metaverses are able to filter inbound objects by those tags</p></figcaption></figure>

### Permissioned Storage

While many NFT projects host their interoperable object content in a publicly accessible location, for many projects this many be unwanted or impractical.

Permissioned Storage is a mechanism where interoperable object content URLs can be placed on-chain, but the underlying asset is not publicly available for download. The service ensures any reader owns the underlying NFT to get access to the content.

{% hint style="info" %}
Permissioned objects are currently in Early Access with our Enterprise partners - please reach out via support if you are interested
{% endhint %}

### Adding Objects to the Network

While the interoperability network is in Early Access we’re interested in any interoperable content, starting with Avatars and Accessories.

If you have an NFT collection you’d like to be part of the network please reach out on Discord!


# Support

If you need help, please either reach out through your support contact.

If you don't have a support channel, jump onto [Discord ](https://discord.gg/2DGTBbRZ)and ask there!

## Related Pages

* [Development Support](/platform-documentation/admins/pricing/development-support)
* [War Room Support](/platform-documentation/admins/pricing/war-room-support)
* [Platform SLA](/platform-documentation/admins/pricing/platform-sla)


# EULA

Morpheus Platform End User License Agreement V 2.0

(last updated 30th May 2025)\\

1. OVERVIEW\\

**1.1 Welcome.** Welcome to MSquared! At MSquared, we appreciate our community's creativity and enthusiasm. Our technology enables developers of all levels to make virtual experiences, events and virtual items and other content for use across virtual worlds (the “MSquared Network”). These Terms are designed to encourage your creativity and contributions while setting boundaries to prevent misuse. Please refer to them to understand what is allowed and what is not, ensuring that you can create and share your work confidently. If you are considering an action not explicitly addressed in these Terms, and we have not indicated approval, please assume it is not allowed without our written consent.

**1.2 Agreement.** This agreement (the “Agreement”) is between you (“you” or “your” or “User”) and Improbable MV Limited (incorporated in England) with company number 13856337 whose registered office is at 10 Bishops Square, London, E1 6EG, UK (“MSquared”, “we”, “us” or “our”). By downloading or using the MSquared platform software including any related software or features provided by Msquared (the “MSquared Platform”), you acknowledge and accept the following terms (the “Terms”). These Terms are a legally binding contract between you and us, either individually or, if applicable, on behalf of your corporate entity/employer. You can end this Agreement at any time - please see section 12.5 below on how to do this.

**1.3 Purpose.** The purpose of these Terms is to enable you to use the MSquared Platform to: (i) develop Virtual Items for your own or third party Virtual Experiences (as defined below); and (ii) develop, host and operate your own Virtual Experiences, in each case on the MSquared Network and for both non-commercial and commercial purposes, in accordance with our rules, guidelines, policies and requirements (the “Purpose”).

**1.4 Right to Modify Terms.** We have the right to modify these Terms (in whole or in part) from time to time without liability to you. When we modify these Terms we will notify you of the update on our website: <https://docs.otherside.xyz/help/end-user-license-agreement-eula> (the “Website”). Your continued use of the MSquared Platform following such notification shall be deemed to be your acceptance of such revised Terms.

2. KEY TERMS SUMMARY

You should read all of the Terms. To help guide you, we have summarised some of the key points for your convenience. Please note the full terms and conditions still apply, as set out below.\\

**2.1 Access.** In order to access the MSquared Platform:

* Age - you must be at least 18 years old.
* Authority - If you are acting on behalf of your employer, you must be authorised to enter into these Terms on behalf of your employer.
* Account - you will need to create an account on the MSquared Platform. You confirm that the email address and information you use for registration with us is, and shall remain, true and accurate and complete at all times. We reserve the right to suspend or terminate your account if we reasonably believe that the information you provide to us is not accurate or is not your own. You agree to keep your log-in details confidential and not to share them with anyone else. You accept full responsibility if you fail to do this and your account is accessed by a third party.

**2.2 Unreal Licence.** You will need to have your own separate and standalone Unreal Engine 5 licence (see <https://www.unrealengine.com/>) and, at all times, comply with the Unreal Engine EULA (<https://www.unrealengine.com/en-US/eula/unreal>). You will also need to accept the terms of service and privacy policy that governs the use of Epic Online Services (<https://dev.epicgames.com/en-US/services-games>).

**2.3 Development Status.** The MSquared Platform software is a continually evolving product. Therefore, there may be missing or incomplete features, bugs or errors which may be subject to further testing, development, patches and/or updates in our sole discretion. We do not make any promises, warranties or representations of any kind about (or accept any liability for) the MSquared Platform, what it does, how it does it, or about future content. The MSquared Platform is provided “as is” and without warranty or representation, express, implied or statutory, including (without limitation) warranty as to satisfactory purpose, merchantability, fitness for any particular purpose or availability for use; nor are there any warranties created by course of dealing or course of practice, performance or trade usage. All implied and/or statutory representations, conditions or warranties are excluded to the extent permissible by law.

**2.4 Availability / Downtime**. The availability service levels for the MSquared Platform can be accessed here: <https://status.msquared.io/>. In addition, there may be times when the MSquared Platform (or any part of it) is not available due to planned maintenance. Where possible we will try to give notice in advance of any planned downtime via the Website.

**2.5 Updates.** We are constantly developing and improving the MSquared Platform. Where we plan on making a material change to the MSquared Platform (for example if we plan to deprecate a MSquared Platform API), we will use our best efforts to give you at least three (3) months notice of such planned change. We will provide such advance notice in the release notes for the MSquared Platform. Where we need to make an emergency or unplanned material change to the MSquared Platform, we will use our best efforts to give you as much prior notice as possible.

**2.6 Fees.** The fees for access to the MSquared Platform and related services are set out here: <https://docs.otherside.xyz/admins/pricing> and may be updated from time-to-time.

**2.7 Account Suspension / Termination.** It is very important that you comply with the full Terms, including our Acceptable Use Policy set out in Appendix 1. Your failure to do so may result in us suspending or terminating your account and your access to the MSquared Platform. See section 9 (Term, Suspension and Termination) below for more details.

**2.8 Interoperability.** One of the core concepts of the MSquared Network is the interoperability of virtual items, enabling them to move freely between different Virtual Experiences on the network. When you operate a Virtual Experience or create Content, you will need to comply with our Interoperability requirements set out in section 8 (Interoperability) below.

**2.9 Privacy.** Our [Privacy Policy](https://www.improbable.io/privacy-policy) sets out how we collect, use and process your personal data when you sign up for an account with, and subsequently access, the MSquared Platform. If you do not agree to our Privacy Policy, you should not download or access the Mquared Platform.

3. DEFINITIONS

“Collaborators” is defined in section 5.4 below.

“Content” is defined in section 4.1 below.

“End User” means any participant, player or other end user of a Virtual Experience.

“Project” means each project and all work in progress that you store on our platform via your account with us.

“Virtual Experience” means a creatively autonomous, interoperable virtual world (or group of virtual worlds) built using the MSquared Platform and hosted on the MSquared Network which can be persistent or which can be time limited events or experiences.

“Virtual Items” means: (i) End User characters or avatars; (ii) “companion” or “follower” non-End User characters programmed to travel with a End User character or avatar; (iii) clothes or wearable items for End User characters or avatars; and (iv) transportable or portable tools, instruments, and other non-environmental items carried or borne by End User characters or avatars, and which, in each case, are interoperable across the MSquared Network.\\

4. INTELLECTUAL PROPERTY OWNERSHIP

**4.1 Owned by you.** You retain ownership of all intellectual property rights in: (a) all Virtual Items and Virtual Experiences that you either develop, upload, import, or otherwise make available on or through the MSquared Platform; and (b) all of your trade marks, logos and other brand assets you incorporate in any of the foregoing, including, in each case, any modifications, improvements or enhancements to the same (collectively your “Content”).

**4.2 Owned by us.** We retain ownership of all intellectual property rights in: (a) the MSquared Platform; and (b) any other assets (audio and/or visual), tools or software that we may make available via the MSquared Platform (together the “Improbable Property”), which we may make available to you via the MSquared Platform. Notwithstanding the restrictions set out in section 5 (Licences) below, to the extent that you make (or engage a third party to make) any modifications to, or any derivative works from, any of the Improbable Property, you hereby assign to us by way of present assignment of present and future rights all right, title and interest in and to all such modifications and derivative works and you agree that you will do all such things and take all such actions as we reasonably require in order to transfer such modifications and derivative works, and the intellectual property rights in them, to us. We separately retain ownership of our name, logo and associated trade marks, which we may make available to you upon written request and subject to a separate agreement.

**4.3 Feedback and Suggestions.** MSquared will own all rights in all oral and written feedback, ideas, proposals, or suggested improvements relating to any MSquared innovations, including the MSquared Platform along with other MSquared products and services. If you offer us a suggestion, please understand that you're doing so voluntarily and without any expectation of compensation. We are not required to review or use your suggestion, and if we choose to use it, we are not obligated to pay you. If you believe your suggestion has value that warrants payment, please inform us of your expectation to be paid before you share your idea. We will then let you know in writing if we are interested and agree to consider it under those terms.

**4.4 DMCA and Infringing Content.** In accordance with the Digital Millennium Copyright Act of 1998, the text of which may be found on the U.S. Copyright Office website at <http://www.copyright.gov/legislation/dmca.pdf>, MSquared will respond expeditiously to claims of copyright infringement committed using the MSquared Platform if such claims are reported to MSquared. Upon receipt of: (i) a copyright infringement notice; (ii) evidence of breach of the Acceptable Use Policy; and/or (iii) evidence of breach of any applicable moderation guidelines, MSquared will take whatever action, in its sole discretion, it deems appropriate. Such action may include: (A) removal of the challenged content from your Content within the MSquared Platform in which such products are made available; and (B) blocking access to Virtual Experiences on the MSquared Network.

5. LICENCES

**5.1 Licence to Use the MSquared Platform.** Subject to your compliance with these Terms (including the Acceptable Use Policy), MSquared grants you a personal, worldwide, non-exclusive, non-transferable, non-sublicensable, revocable limited right and licence for the term of this Agreement with you to install and use the MSquared Platform for the Purpose on compatible devices you own or control.

**5.2** You must not (unless we expressly agree otherwise in writing with you): (i) copy, modify, merge, distribute, translate, reverse engineer, decompile, disassemble, hack or interfere with the MSquared Platform editor code or any part of it; (ii) use the MSquared Platform, or upload or make available on the MSquared Network any Content which in any way which breaches the Acceptable Use Policy; or (iii) use our MSquared Platform to make or operate a competing virtual worlds platform.

**5.3 Sublicensing.** The rights given to you pursuant to these Terms are personal to you and cannot be transferred or sublicensed unless we expressly agree otherwise in writing. If you wish to collaborate with others on your Project(s) please see section 6 (Collaboration) below.

**5.4 Your Licence to MSquared.** You grant MSquared a royalty-free, perpetual, irrevocable, sub-licensable, worldwide, and non-exclusive licence to make your Content available to End Users on the MSquared Platform. This licence allows MSquared to:

**5.4.1** Publish, copy, update, modify, display, stream and distribute your Content in order to enable your Content to be made available to End Users;

**5.4.2** Host your Content on the MSquared Network to make it accessible and available to you and to End Users;

**5.4.3** Display, use, promote, market in any media, distribute, or perform your publicly available Content in any way we see fit; this includes displaying such Content in screenshots, videos, live events, trade shows, tournaments, and other materials that promote the MSquared Platform; and

**5.4.4** Provide other End Users and us with the right to stream or record gameplay of your publicly available Content (“Videos”), edit these Videos, and promote these Videos using images of gameplay (which may also include depictions of your Content) on social media and other video sharing platforms.

6. COLLABORATION

**6.1** You can invite third parties to collaborate on your Projects and/or to create Content for your Events and/or Virtual Experience (“Collaborators”) provided that:

**6.1.1** Each Collaborator must separately enter into and comply with these Terms. We can suspend or terminate a Collaborator’s use of the MSquared Platform if they breach these Terms, in accordance with Section 11 (Term, Suspension and Termination) below;

**6.1.2** You accept sole responsibility for the acts or omissions of your Collaborators and you are responsible for ensuring their compliance with these Terms. If a Collaborator’s acts or omissions cause you any loss or harm in connection with their access to your Projects or their use of the MSquared Platform as your Collaborator, you agree to bring any claim for losses or damages against them and not us; and

**6.1.3** You are liable to us for any Fees incurred by your Collaborators in connection with your Projects and their use of our Services.

**6.2** You can determine the level of access to give each Collaborator in your account settings. You can change the level of access given to a Collaborator, and you can block or remove a Collaborator's access to your Projects, at any time via your account settings.

7\. CONTENT

**7.1 Experience Creation** - All Virtual Experiences you create on the MSquared Network must comply with the terms of this Agreement, including the Acceptable Use Policy set out in Appendix 1.

**7.2 Content Creation** - All Virtual Items that you create for use in, or import into, the MSquared Network must comply with the following principles:

**7.2.1 Creation** - All Virtual Items created in the MSquared Network must be created using the MSquared Platform’s object creation tools and in accordance with the MSquared Network’s open object standards;

**7.2.2 Import** - All virtual assets and items imported into the MSquared Network must be converted into Virtual Items using MSquared Platform’s object creation tools and in accordance with the MSquared Network’s open object standards; and

**7.2.3 Interoperability** - All Virtual Items are intended to be a core part of an End User’s identity across Virtual Experiences on the MSquared Network. These objects are intended to be available to the End User owner across all Virtual Experiences on the MSquared Network, subject only to the moderation settings determined by the Virtual Experience operator set out below.

**7.3** You are solely responsible for the Content that you or your Collaborators develop using the MSquared Platform. As your Content will be uploaded to and made available via the MSquared Network, you promise to us that:

**7.3.1 Authority** - You have the right to use all assets and content (including all third party assets and content) that you include in your Content;

**7.3.2 Non-Infringement** - Your Content does not infringe or violate the rights of any third parties, including intellectual property, publicity, privacy, or any applicable laws, or the obligations outlined in these Terms;

**7.3.3 Music Rights** - To the extent your Content contains music, you promise to us that you: (i) either fully own the music or have obtained the necessary consents and licences to upload and use the sound recording(s); (ii) we are authorised to make such music available via your Content on the MSquared Network without the payment of any licensee, royalty or any other fees to you or any third party; and (iii) will comply with any relevant reporting requirements or contractual obligations to entities such as labels, publishers, performing rights societies, collective management organisations, co-writers, performers, and any other applicable payments or fees to organisations like SAG-AFTRA and/or AFM. If you cannot secure and uphold these consents or licences for MSquared’s benefit, you will not upload music to MSquared; and

**7.3.4 Harmful Code** - Your Content does not contain viruses, harmful code, malware, spyware, corrupted data, or other elements that could negatively affect how other End Users enjoy the MSquared Platform or the MSquared Network.

**7.4 Responsibility for Content** - You are solely and exclusively responsible for: (i) all legal and regulatory compliance; and (ii) any End User and/or consumer matters which arise from or in connection with any Content you make available (or allow others to make available) in your Virtual Experience, and you acknowledge that MSquared has no responsibilities, obligations or liabilities whatsoever regarding the same. This includes (but is not limited to) applicable consumer protection, data protection and other regulatory matters (as well as customer support, dealing with all sales, refunds, rebates, taxes and fees relating to sales).

**7.5** You acknowledge that we reserve the right to remove or modify any Content in our sole discretion if we reasonably believe we need to due to any applicable laws, regulations, or policies that would risk liability to, including compromising the integrity of, the MSquared Platform.

**7.6** You will make regular back-up copies of your Content. We have no responsibility or liability for your Content or the material others upload, store or share using the MSquared Platform.

8\. INTEROPERABILITY

**8.1** All Virtual Items must comply with the following interoperability principles:

**8.1.1** Subject to the moderation setting you select for your Virtual Items, all Virtual Items you create or import (or allow your End Users or Collaborators to create or import) to the MSquared Network (including your Virtual Experience) must:

1. comply with the MSquared Network’s open object standards and not technically restrict End Users from accessing, displaying or using such Virtual Item in any other Virtual Experience on the MSquared Network; and
2. be gifted, sold or licensed to End Users under terms that, at a minimum, grant the acquiring End User (and each subsequent owner of the Virtual Item) a worldwide, personal, non-commercial licence to access, use and display the relevant Virtual Item (including designs, drawings, artwork, text, images, and video linked with, or incorporated into such Virtual Item) within any Virtual Experience in the MSquared Network.

**8.1.2** Subject to the moderation settings for your Virtual Experience, you must:

1. enable and permit all third party Virtual Items to access, be displayed in and be used by End Users in your Virtual Experience;
2. treat all Virtual Items equally within your Virtual Experience, whether they are first party Virtual Items created in or imported into your Virtual Experience or third party Virtual Items; and
3. not apply any charges, limitations or restrictions on End Users for using Virtual Items in any public areas in your Virtual Experience. However, you may restrict the use of interoperable Virtual Items in certain private areas or events within your Virtual Experience (for example, to create VIP areas), provided such restrictions are clearly disclosed, and not designed to circumvent interoperability requirements.

**8.2.** We are authorised to take all steps we deem reasonably necessary to enforce your (and your End Users’ and Collaborators’) compliance with the interoperability provisions on the MSquared Network to ensure that Virtual Items can move freely between Virtual Experiences (subject to applicable moderation settings). \\

9\. MODERATION

**9.1** The MSquared Platform will enable Virtual Experience Owners to deploy Moderation settings to ensure that their Virtual Experiences and the content used and displayed within them are appropriate to the Virtual Experience’s audience. They are intended to work in the same way as a PEGI or similar ratings system and will apply at a category level. They cannot be used to block competitor brands or individual Virtual Items. You are responsible for ensuring that End Users are aware of the age rating and any moderation settings that you decide to apply to either a Virtual Experience or Virtual Items that End End Users may wish to use in your Virtual Experience.

**9.2 Content.** Content moderation settings can be applied at two levels:

**9.2.1 Moderation settings applied to Virtual Items** - you can apply moderation settings to a Virtual Item when it is imported or created by applying certain descriptors to it. For example, if you create a family friendly Virtual Item, you may want to prohibit it from appearing in a Virtual Experience aimed just at adults. If you wish to block certain Virtual Items in your Virtual Experiences, then you need to clearly state why such Content is blocked as part of the registration process for your Virtual Experience.

**9.2.2 Moderation settings applied to Virtual Experiences** - you can apply moderation settings to your Virtual Experience. If a Virtual Item is not compatible with the moderation guidelines set by you, then it will not be allowed to appear in your Virtual Experience. For example, if your Virtual Experience is intended to be a family friendly space, you may restrict violent or adult content Virtual Items from appearing in your Virtual Experience.

**9.3 Technical Moderation.** You can contact us at [contact@msquared.io\[INSERT LINK\] ](mailto:contact@msquared.io)if you believe you have a case where you need to apply technical moderation in your Virtual Experiences (for example, adding restrictions on minimum/maximum avatar sizes). Please set out in detail the limitations you require, as well as your reasons for such restrictions and one of our technical team will contact you to support you to see about granting an exception. This work may be chargeable; if it is then we will agree to any charges with you upfront.

**9.4 Communications.** You may also use our internal moderation tools (or any approved third party moderation tools that we may make available via the MSquared Network) to set and enforce moderation policies for voice and chat within your Virtual Experience.

10\. FEES

**10.1** The fees for your use of the MSquared Platform (the “Platform Fees”) and related services are set out here: <https://docs.otherside.xyz/admins/pricing>. These prices may be updated from time to time.

**10.2 Making Payment.** We will invoice you monthly for all costs incurred by you and your Collaborators against your account in the previous month.

**10.3 Payment Terms.** All amounts due or payable to MSquared in connection with your use (and the use by your Collaborators) of our paid-for Services shall be paid by you within thirty (30) days of being billed for such Services. Interest shall accrue on all amounts not paid by the applicable due date at a rate, calculated upon the unpaid balance, at the rate of three (3) per cent per annum above the base rate for the time being of Barclays Bank plc. All payments made hereunder shall be payable in United States Dollars; all revenues realised in other currencies shall be converted to United States Dollars at the officially published average exchange rates of the reporting period (using rates published by an international bank or recognized exchange rate website - [www.oanda.com](http://www.oanda.com), [www.x-rates.com](http://www.x-rates.com), [www.xe.com](http://www.xe.com), [www.thomsonreuters.com](http://www.thomsonreuters.com)). All payments made by you to us will be sent by wire transfer to the account of our choosing as will be separately indicated to you.

**10.4 Taxes.** You are responsible for the payment of all taxes which may arise from your Virtual Experiences and the creation and/or sale of your Virtual Items in connection with such experiences.

11\. LIABILITY.

**11.1** If you breach these Terms, including (without limitation) by failing to pay when due any fees or charges, and it causes us harm or financial loss, you agree to compensate us for all related losses, claims, and expenses. Examples of breaches include (but are not limited to): (i) unauthorised use of (a) the MSquared Platform, (b) our confidential information, or (c) third-party content in your Virtual Experiences; (ii) any third-party intellectual property claims; and (iii) any other violations of these Terms.

**11.2** We are not liable for claims based on modifications to the MSquared Platform made by others, combining our platform with other software, not using the latest version of the MSquared Platform that we provide, or matters outside our control. We will not be responsible for property damage, loss of earnings, profits, charges, expenses, data loss, business loss, reputational harm, or any indirect, special, or consequential damages. This exclusion applies regardless of the cause of action, including breach of contract, tort, negligence, or misrepresentation. However, nothing in these Terms limits liability for fraud, willful misconduct, or death or personal injury caused by negligence.

**11.3** Our total liability will be the greater of USD$100 or the amount you paid in Platform Fees in the calendar year in which you make a claim.\\

12\. TERM & TERMINATION

**12.1 Term.** These Terms shall apply from the date you register your account on the MSquared Platform and shall continue until you notify us of your intention to cease using the MSquared Platform (as set out in section 12.5 below), unless terminated earlier by us as permitted by these Terms or if we decide to shutdown the MSquared Platform.

**12.2. Suspension By us.** We may suspend access to your account, the MSquared Platform and/or your use of some or all of your Content on the MSquared Network if we believe you are, or will be, in breach of these Terms (including, but not limited to, the Acceptable Use Policy). We will notify you of the reason for your suspension and will give you a reasonable period to remedy the cause of your suspension (if, in our view, it is capable of remedy). If you fail to remedy the cause of your suspension within the stated period, we may terminate your access to your account, the MSquared Platform and/or your use of some or all of your Content on the MSquared Network without further notice to you.

**12.3 Termination By us.** Unless we and you have agreed otherwise in writing, we may terminate your access to your account, the MSquared Platform and/or your use of some or all of your Content on the MSquared Network on thirty (30) days written notice by us to you at any time.

**12.4. Termination For Inactivity.** We retain the right to terminate any accounts, in our sole discretion, where an account has been inactive for a period of at least ninety (90) days.

**12.5 Termination By you.** You may terminate these Terms at any time by notifying us by email at <contact@msquared.io> and by deleting your account and permanently ceasing all use of the MSquared Platform. In the event that you notify us under this section 12.5 and fail to delete your account, we reserve the right to delete your account at any time following receipt of such notice.

13\. CONSEQUENCES OF TERMINATION

**13.1** In the event of termination of these Terms:

**13.1.1** You must immediately cease using the MSquared Platform, and uninstall it from your devices as well as those of your Collaborators. You and your Collaborators will no longer be able to access your account and you will not be able to operate your Virtual Experience or create or sell new Virtual Items; and

**13.1.2** All Content developed by you will be retained by you. To the extent that you have provided us with any of your Content (for example, in executables) please contact <contact@msquared.io> to arrange the return or destruction of such code - we reserve the right to remove your Virtual Experience from the MSquared Platform in a reasonable timeframe following termination and, to the extent possible, we will give you advance notice before such removal. Our rights to use footage of your Content in Videos and other promotional and marketing materials shall survive the termination of these Terms.

**13.2** Any of your Virtual Items which were sold/gifted to End Users prior to termination may continue to be used and/or (where such Virtual Items have the ability to be resold/regifted to other End Users) resold/regifted across the MSquared Network by the End Users who own such items.

**13.3** All amounts due to us must be paid prior to the final termination date.

**13.4** Once you have exited the MSquared Platform, all rights and obligations of the parties will cease to have effect, save for: (i) any and all accrued rights and obligations of the parties at the termination date; and (ii) those rights and obligations of the parties necessary for the interpretation and enforcement of it.

14 DATA PROTECTION

In relation to data protection matters, you will be the data controller and MSquared will be the data processor. You will be responsible for the use of and entry into any applicable end user licence, privacy policy, acceptable use policy and other applicable documentation between you and End Users and/or consumers.

When you create an account on, or otherwise access, the MSquared Platform you will be required to provide to us, and we will process certain personal data. For information regarding the collection, processing, and use of your personal data please read our [Privacy Policy](https://www.improbable.io/privacy-policy). If you do not agree to our Privacy Policy you should not download or access the MSquared Platform.

15\. GENERAL

**15.1 Assignment.** You may not assign or transfer your rights and obligations to any other person for any reason, and any attempt will be considered void and result in the termination of these Terms.

**15.2 Governing Law and Dispute Resolution.** These Terms will be governed by and construed in accordance with the laws of England and Wales. Any dispute arising from or related to these Terms will be subject to the exclusive jurisdiction of the courts of England and Wales. Your local law may give you rights that these Terms cannot change; if so, these Terms apply as far as the law allows.

**Appendix 1: Acceptable Use Policy**

You are responsible for your behaviour and the Content you share when using the MSquared Platform. We want all Users to enjoy the MSquared Platform, show their creativity and feel like they belong to our international community. As a result, we do not want anything harmful or inappropriate on the MSquared Platform. To protect our community, you must not violate the Terms and by agreeing to the Terms, you agree to abide by the following rules:

1. Not to use the MSquared Platform in a way that violates the Unreal Engine licence terms or these Terms;
2. Not to sell, rent, lease, licence, distribute or otherwise transfer the MSquared Platform in whole or parts;
3. Not to copy, reproduce, or archive the MSquared Platform (or any part of it);
4. Not to reverse engineer, derive source code from, modify, adapt, translate, decompile, disassemble, hack or otherwise interfere with the MSquared Platform or make derivative works based on the MSquared Platform;
5. Not to breach any security or authentication measures in the MSquared Platform;
6. Not to use the MSquared Platform to create or operate a competing virtual world platform to the MSquared Platform;
7. Not to send spam, engage in phishing, or create or spread malware;
8. Not to display or share inappropriate and / or illegal content, such as bestiality, pornography, offensive language, graphic violence, or content promoting self-harm or criminal activity;
9. Not to participate in or promote illegal, fraudulent or misleading activities, such as impersonating others, creating fake accounts, or manipulating service metrics;
10. Not participate in, promote or provide a real money gambling service or virtual experience using or connected to the MSquared Platform (unless you have all the necessary permissions, licences and consents to do so, which will need to be shared with us for verification and maintained for the duration of your use of our platform); and
11. Not to invade others' privacy or otherwise infringe any End User’s rights to privacy.
12. Not to use the MSquared Platform in a manner which seeks to harm, misuse, damage or otherwise negatively impact either the MSquared Platform itself or the experience that the MSquared Platform provides to other End Users.
13. You agree to prevent any harassment, trolling or any other negative behaviour between End Users within your Virtual Experience.
14. You agree to set appropriate moderation settings for your Virtual Experience, based on the audience you are targeting for your Virtual Experience. For example, if you are providing a Virtual Experience aimed at families, you will set appropriate moderation settings to prohibit adult content themed Virtual Items from entering such experience.

To keep the community welcoming and inclusive for everyone, we have a zero-tolerance policy towards all illegal and inappropriate activity, including hate speech, terrorist or violent extremist content, bullying, harassing, sexual solicitation, fraud, or threatening others.

Please watch out if you are talking to other people in MSquared. It is hard for either you or us to know for sure that what other people say is true, or even if people are really who they say they are. We advise you not to give out any of your personal information.

To report misuse or a breach of this Acceptable Use Policy, please contact <legal@improbable.io> with relevant details (e.g. description, screenshots, or links). We will review all reports and take appropriate action as needed.

\\


# Firewall Problems

Guidance for accessing the M² platform via a corporate firewall

## Native Clients

Downloading and running the MSquared client requires access to [GCP](https://cloud.google.com/). Some corporate firewalls block access to certain ports or IPs used by GCP, which can prevent this working. You will need to allow the following ports and IPs:

#### Ports

`7000-7100`

<details>

<summary>IP Allow List</summary>

8.34.208.0/23

8.34.211.0/24

8.34.220.0/22

23.251.128.0/20

34.14.0.0/17

34.22.112.0/20

34.22.128.0/17

34.34.128.0/18

34.38.0.0/16

34.52.128.0/17

34.53.128.0/17

34.62.0.0/16

34.76.0.0/14

34.118.254.0/23

34.140.0.0/16

35.187.0.0/17

35.187.160.0/19

35.189.192.0/18

35.190.192.0/19

35.195.0.0/16

35.205.0.0/16

35.206.128.0/18

35.210.0.0/16

35.220.96.0/19

35.233.0.0/17

35.240.0.0/17

35.241.128.0/17

35.242.64.0/19

104.155.0.0/17

104.199.0.0/18

104.199.66.0/23

104.199.68.0/22

104.199.72.0/21

104.199.80.0/20

104.199.96.0/20

130.211.48.0/20

130.211.64.0/19

130.211.96.0/20

146.148.2.0/23

146.148.4.0/22

146.148.8.0/21

146.148.16.0/20

146.148.112.0/20

192.158.28.0/22

34.1.224.0/19

34.12.0.0/16

34.13.128.0/17

34.32.128.0/17

34.34.0.0/17

34.90.0.0/15

34.104.126.0/23

34.124.62.0/23

34.141.128.0/17

34.147.0.0/17

34.153.45.0/24

34.153.237.0/24

34.157.80.0/23

34.157.92.0/22

34.157.208.0/23

34.157.220.0/22

35.204.0.0/16

35.214.128.0/17

35.220.16.0/23

35.234.160.0/20

35.242.16.0/23

</details>

## GeForce Now

### Ports

In order to access events & content via Nvidia's GeForce Now pixel streaming, you should open the following ports:

> * 49003 – UDP Inbound AUDIO
> * 49004 – UDP Outbound AUDIO
> * 49005 – UDP Inbound VIDEO
> * 49006 – TCP/UDP Outbound/Inbound Remote Input
>
> If GeForce NOW is running behind a corporate or school firewall, the firewall should allow traffic to/from GeForce NOW servers’ UDP ports between 10000 and 20000.

(See [this Nvidia support page](https://nvidia.custhelp.com/app/answers/detail/a_id/4504/~/how-can-i-reduce-lag-or-improve-streaming-quality-when-using-geforce-now%3F) for more context.)

### Testing

You can check whether your network is blocking pixel-streaming traffic by trying to access an event or content and seeing whether you are presented with an error similar to:

<figure><img src="/files/EoYVDm6qZO9RnmhyzUhS" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you are reading this page in preparation for a platform evaluation - you can see if pixel streaming is blocked on your network by going to <https://construct.msquared.io/> > `Construct Main` > `Play Now` > `Join on web` > `GeForce Now` and seeing whether you experience the error above
{% endhint %}

### Availability

As of 2025, GeForce Now should cover all major regions in the world, specific data centers are listed below, if your desired country is not listed GFN may still be accessible in those countries. For additional testing, we would recommend using a VPN and accessing the Construct test map mentioned above:

<details>

<summary>GFN On-demand datacentres</summary>

* Åland (Finland)
* Albania
* Andorra
* Anguilla
* Australia - Served via Japan
* Austria
* Bahamas
* Barbados
* Belgium
* Bermuda
* Bosnia and Herzegovina
* British Virgin Islands
* Bulgaria
* Canada
* Canary Islands (Spain)
* Cayman Islands
* Croatia
* Cyprus
* Czechia (Czech Republic)
* Denmark
* Dominica
* Dominican Republic
* El Salvador
* Estonia
* Faroe Islands
* Finland
* France
* Germany
* Gibraltar
* Greece
* Greenland
* Guadeloupe
* Guatemala
* Guernsey
* Honduras
* Hong Kong
* Hungary
* Iceland
* Ireland
* Isle of Man
* Israel
* Italy
* Japan
* Jamaica
* Jersey
* Latvia
* Liechtenstein
* Luxembourg
* Malta
* Martinique
* Mexico
* Monaco
* Mongolia
* Montenegro
* Montserrat
* Morocco
* Netherlands
* North Macedonia
* Norway
* Philippines
* Poland
* Portugal
* Puerto Rico
* Republic of Lithuania
* Romania
* Saint Barthélemy
* Saint Pierre and Miquelon
* Serbia
* Slovakia
* Slovenia
* Spain
* Sweden
* Switzerland
* Taiwan
* Trinidad and Tobago
* Tunisia
* Turks and Caicos Islands
* U.S. Virgin Islands
* Ukraine
* United Kingdom
* United States

</details>

In the follow countries, the service is only available via GeForce Now partners, and capacity must be reserved in advance:

<details>

<summary>Reserved capacity partners &#x26; countries</summary>

GFN.CO.KR

* South Korea

AU

* Japan

GAME+

* Turkey
* Cyprus

Zain

* Jordan
* Kuwait
* Oman
* Saudi Arabia

ABYA

* Argentina
* Brazil
* Chile
* Paraguay
* Uruguay

StarHub

* Singapore
* Indonesia
* Thailand
* Vietnam

Yes

* Malaysia

GFN.AM

* Armenia
* Azerbaijan
* Georgia
* Kazakhstan
* Moldova
* Ukraine
* Uzbekistan

rain

* South Africa

</details>


# Unreal Development

Morpheus Platform worlds are built on top of [Unreal Engine](https://www.unrealengine.com/).

To develop for Morpheus Platform, you'll need to work with Unreal.

{% content-ref url="/pages/Y6HEvhcJnAuD9TCPLcko" %}
[Get Started](/platform-documentation/creation/unreal-development/getting-started)
{% endcontent-ref %}


# Get Started

Practical steps to start building your experience in Unreal

{% content-ref url="/pages/lsDzWbPCoSnWWhJ7lbBi" %}
[Download the Editor](/platform-documentation/creation/unreal-development/getting-started/downloading-the-tooling)
{% endcontent-ref %}

{% content-ref url="/pages/esgUhyLQgeUsn9Npft1e" %}
[Morpheus Base Project](/platform-documentation/creation/unreal-development/getting-started/using-the-template-project)
{% endcontent-ref %}

{% content-ref url="/pages/rPfh6dxLDqPZDobA16WW" %}
[Differences from Unreal](/platform-documentation/creation/unreal-development/getting-started/differences-in-unreal-development-workflow)
{% endcontent-ref %}

{% content-ref url="/pages/fnJCIhEkOAuGajUSsVjU" %}
[Morpheus Networking](/platform-documentation/creation/unreal-development/getting-started/networking)
{% endcontent-ref %}

{% content-ref url="/pages/l36BahB26DKwryII6xLd" %}
[Creating a New Map](/platform-documentation/creation/unreal-development/getting-started/creating-your-own-map)
{% endcontent-ref %}

{% content-ref url="/pages/JAbUkjrZ9SayjUEWY0Hl" %}
[Creating a new character](/platform-documentation/creation/unreal-development/getting-started/creating-your-own-character)
{% endcontent-ref %}

{% content-ref url="/pages/MiTAEg2n9Cp9OztMvie2" %}
[Upload Content](/platform-documentation/creation/unreal-development/getting-started/uploading-content)
{% endcontent-ref %}


# Download the Editor

Download our editor to start building Morpheus Platform experiences

## System Requirements <a href="#system-requirements" id="system-requirements"></a>

Currently, the Morpheus Platform editor only supports Windows 10 and 11.

## Hardware Requirements

Our editor is built on Unreal Editor, and has the same [hardware and software requirements](https://dev.epicgames.com/documentation/en-us/unreal-engine/hardware-and-software-specifications-for-unreal-engine).

{% hint style="info" %}
The exact hardware necessary will depend on the scale of your project.
{% endhint %}

## Prerequisites

* If you haven't installed Unreal before, you might need Microsoft's [Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe).
* You need to have been given `Developer` access to your metaverse - see [Access Control](/platform-documentation/admins/access-control).

## Steps

From your [dashboard](/platform-documentation/morpheus-platform/glossary#dashboard), click the **`⬇ Download the editor`** button.

In the modal dialog that appears, first click the **`⬇ Install the Launcher`** button.

<figure><img src="/files/T2xEFtxaJBc4CZqLkFS1" alt=""><figcaption><p>Note: Your <a href="/pages/2XcNNmRWXjvAM4mmvOvX#projects">project</a> probably won't be called Staging. If you're not sure which project's editor to download, speak to your colleagues.</p></figcaption></figure>

{% hint style="info" %}
M² Launcher is our thin desktop app for Unreal editor & client downloading. It caches downloads so that subsequent launches happen fast.

i.e. The second time you click ⬇ **`Download`** for the same editor or client version, it should run immediately.
{% endhint %}

{% hint style="warning" %}
To change the editor download location (e.g. drive) - see the [M2 Launcher](/platform-documentation/apis-and-tooling/launcher) page.
{% endhint %}

After installing the launcher, click the bigger ⬇ **`Download`** button to get the editor.

The launcher should automatically appear and start the editor download.

<figure><img src="/files/7O4bNWgbTRA6RZLfISLD" alt=""><figcaption><p>Downloading an editor</p></figcaption></figure>

Once the download finishes, it should automatically open the Unreal Engine project modal.

If it doesn't, you can click the ⬇ **`Download editor`** button in the dashboard again, and now the launcher will immediately open Unreal.

To create a new project:

* Click the `MSquared` tab
* Configure the Project Location and Project Name as you like
* Click the `Create` button

{% hint style="info" %}
For further details on how to use the template project, see [Morpheus Base Project](/platform-documentation/creation/unreal-development/getting-started/using-the-template-project)
{% endhint %}

<figure><img src="/files/iIrsxXjKm1ZTDIO0iuj2" alt=""><figcaption><p>Creating a project</p></figcaption></figure>

Alternatively, you may want to [open an existing project with this editor](/platform-documentation/creation/unreal-development/tutorials/upgrade-the-editor).

{% hint style="info" %}
In future, you can open your project just by clicking the `uproject` file in your project folder. You should only need to use the download path when if you need to update the editor version you're using. [More on that later](/platform-documentation/creation/unreal-development/tutorials/upgrade-the-editor).
{% endhint %}

## Wait for the Editor to Open

Lastly, wait for the Unreal Editor to open your project.

<figure><img src="/files/F0ulJrC40O3249Pb3Bst" alt=""><figcaption><p>The editor opening a project</p></figcaption></figure>

<figure><img src="/files/ewm4aQi4Hti2kpXtWG60" alt=""><figcaption><p>Your first MSquared project</p></figcaption></figure>

## Known Issues

#### Editor downloads but errors when trying to open

You may need to install the Unreal Engine prerequisites. You can do this by:

1. Navigating to`%LocalAppdata%/M2Launcher/Editors`
2. Sort this directory by Date modified and open the most recently modified directory (this is the Editor that you most recently downloaded).
3. From here, navigate to`Windows/Engine/Extras/Redist/en-us`
4. and run the `UEPrereqSetup_x64.exe` file.


# Morpheus Base Project

{% hint style="success" %}
verified: 2025-11-18 version: v39
{% endhint %}

<figure><img src="/files/iFYF8JbO2X4nY3zUOoI3" alt=""><figcaption></figcaption></figure>

## Summary

The `M² Base Project` contains starting assets. They have sensible default values but you're free to modify and experiment with them.

<figure><img src="/files/G9nvg7x2JqKkxu9QbLRQ" alt=""><figcaption></figcaption></figure>

The `M² Base Project` also contains [Live Config](/platform-documentation/creation/unreal-development/features-and-tutorials/live-config) files, which act as the starting point for making changes to MSquared's default values.

<figure><img src="/files/71gXHTB9HIWTKm1YRNuw" alt=""><figcaption></figcaption></figure>

## Important Assets

* `BP_Example_PlayerController` and `BP_Example_PlayerCharacter` are your player controller and pawn respectively. These behave the same as the native Unreal equivalents, except that they perform no replication.
* `BPM_Example_PlayerCharacter` is your character's Morpheus Actor. This is tied to your pawn, and handles all the replication. For more details on this, see [Introduction to Morpheus Networking](/platform-documentation/creation/unreal-development/getting-started/networking/networking).
* `WBP_Example_HUD` is your HUD widget. This is the recommended location to place any UI elements you want to add.
* `[Your project path]/Config/LiveConfig/Overrides/game.override.json` is where you can override "game" live config values, which will encompass most live config values you will likely make changes to. Some starting overrides have been made here already, such as configuring the quickbar, but you can change these or add other overrides if you want changes to MSquared's default behavior here.

The template project is built off of the [Example Plugin](/platform-documentation/creation/unreal-development/features-and-tutorials/the-m2-example-plugin)'s content, including extending from its base classes (e.g. [The Example Character](/platform-documentation/creation/unreal-development/features-and-tutorials/the-m2-example-plugin/the-example-character)). If you want to make a start on modifying and customizing your character, see [Creating a new character](/platform-documentation/creation/unreal-development/getting-started/creating-your-own-character)

Kickstart your project by importing and creating assets in your project's content folder, transforming it into a truly unique experience.

## The Welcome Message

When you first create a project using the template, you will be presented with a welcome message.

<figure><img src="/files/WpRxB05O8WLszszRObuG" alt=""><figcaption></figcaption></figure>

The message will be shown every time the project is opened, until dismissed with "Don't show this again". However, note that the checkbox only applies to you locally, not for all users of your project. If you don't want collaborators to see the message, or want to customize its contents, you can do so in \`Project Settings -> Editor -> M2 Template Creation Settings

<figure><img src="/files/gW7JtqlCpsEkkud8bTD5" alt=""><figcaption></figcaption></figure>




---

[Next Page](/llms-full.txt/1)

