あなたの世界を作りなさい
経済
ZEPETO Mannequin
32分
zepeto mannequinは、着せ替え/販売機能をサーポートするマネキンapiパッケージです。 オリジナル制作アイテムをワールドで販売できるようになります。 zepetoアバターのnpcに指定したコーデを着せたマネキンを作成できます。 マネキン又はオブジェクトとインタラクションし、ファッションアイテムを購入できます。 step 1 インストール window → package manager → を選択し、まず zepeto world パッケージ 1 21 15 以降をインストールしてください。 その後、zepeto module 1 0 8 パッケージをインストールしてください。 ❗️ 注意事項 パフォーマンス向上の一環として、スタンドアロン パッケージは zepeto module 1 0 8 以降のバージョンのパッケージに統合されました。 以前にインストールした zepeto mannquin パッケージを削除してください。 既存のパッケージを削除した後、zepeto module パッケージ バージョン 1 0 8 以降をインストールしてください。 step 2 マネキンに設定するアイテムidを確認する 👍 アイテムid確認する方法 zepeto studioでアイテム商品をクリックするとポップアップするurlで確認できます。 アイテムidを乱数形式でコピーし、unityエディターに貼り付け、その前に「cr 」を貼り付けます。 📘 アイテム制作が初めての方は、次のガイドを参考にしてください \[ create your item ] step 3 マネキンの設定 マネキンオブジェクトは、全部で3つの方法で制作できます。 ❗️ 注意事項 zepeto mannequinはご自身のオリジナル制作のアイテムだけがワールドに設定/販売する事ができます。 審査で拒否されたアイテムは販売する事ができません。 unityのログインが必須の機能です。 マネキン機能を含んだパッケージをpublishする際も編集ログインが必要です。 zepeto template、zepeto model typeのマネキンを使用する場合、マネキンの数だけゼペトキャラクターが入場した程度のリソースを占めて、最適化に影響を与えることができます。 もし、ワールド内に多くのゼペットキャラクター形状のマネキンを入れなければならない場合は、simple typeのマネキンを選択した後、オブジェクトとして zepeto basemodel https //studio zepeto me/console/download/652892d3a80a9f5505ed888f を活用することをお勧めします。 étape 3 1 type simple とても基本的な方式です。mannequin次式で特定オブジェクトを追加し外形を調整できます。 対話するオブジェクトを追加します。 オブジェクトにインタラクションする為、colliderを必ず追加しなければなりません。 is tringeerチェックは必須です。万が一チェックが確認できなければ、自動的にトリッカーとして認識処理されます。 colliderを追加しないままマネキンのコンポーネットを追加する際、下記のような警告が出てきます。 オブジェクトにマネキンコンポーネントを追加します。 please set the following in the inspector icon 基本的にはハンガーアイコンです。カスタムアイコンに変更可能です。 icon position 基本的にマネキンコンポーネントがあるオブジェクトの位置にアイコンが現れます。 position objectを追加後、値を入れると思い通りの位置に現れます。 ids 販売したいファッションアイテムidを作成できます。 empty objectを追加し、オブジェクトの名前をmannequinscriptで作成します。 zepeto> typescriptを追加し、スクリプトの名前をmannequinscriptで作成します。 以下内容通りに作成します。 import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, spawninfo } from 'zepeto character controller'; import { worldservice } from 'zepeto world'; import { itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { object } from 'unityengine'; export default class mannequinscript extends zepetoscriptbehaviour { private previewer mannequinpreviewer; start() { // code that creates a zepeto character based on the logged in id // zepetoplayers instance createplayerwithuserid(worldservice userid, spawninfo default, true); zepetoplayers instance onaddedlocalplayer addlistener(() => { const character = zepetoplayers instance localplayer zepetoplayer character; // add mannequin interactable component character gameobject addcomponent\<mannequininteractable>(); }); // find all mannequin components const mannequins = object findobjectsoftype\<mannequincomponent>(); mannequins foreach(m => { // enter the collider m onactive addlistener(contents => { mannequin openui(contents); const zepetocontext = zepetoplayers instance localplayer zepetoplayer character context; this previewer = new mannequinpreviewer(zepetocontext, contents); this previewer previewcontents(); }); // exit the collider m oncancel addlistener(() => { mannequin closeui(); this previewer? resetcontents(); }); }); } } スクリプトをオブジェクトに追加し、\[▶︎(play)]ボタンを押して実行します。 s tep 3 2 zepeto model type zepetoアバターのnpcにアイテムidを追加し、マネキン外形を変更することができます。 empty objectを生成して、名前をmannequinで作成します。 オブジェクトにインタラクションする為、colliderを必ず追加しなければなりません。 is tringeerチェックは必須です。万が一チェックが確認できなければ、自動的にトリッカーとして認識処理されます。 オブジェクトにマネキンコンポーネントを追加します。 please set the following in the inspector icon 基本的にはハンガーアイコンです。カスタムアイコンに変更可能です。 icon position 基本的にマネキンコンポーネントがあるオブジェクトの位置にアイコンが現れます。 position objectを追加後、値を入れると思い通りの位置に現れます。 ids 販売したいファッションアイテムidを作成できます。 pose ポーズを選択し、設置できます。 今後、思い通りのポーズをポーズid形態に提供する予定です。 👍 シーンにマネキン スクリプトが既にある場合は、後の手順を省略できます。 empty objectを追加し、オブジェクトの名前をmannequinscriptで作成します。 zepeto> typescriptを追加し、スクリプトの名前をmannequinscriptで作成します。 以下内容通りに作成します。 import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, spawninfo } from 'zepeto character controller'; import { worldservice } from 'zepeto world'; import { itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { object } from 'unityengine'; export default class mannequinscript extends zepetoscriptbehaviour { private previewer mannequinpreviewer; start() { // code that creates a zepeto character based on the logged in id // zepetoplayers instance createplayerwithuserid(worldservice userid, spawninfo default, true); zepetoplayers instance onaddedlocalplayer addlistener(() => { const character = zepetoplayers instance localplayer zepetoplayer character; // add mannequin interactable component character gameobject addcomponent\<mannequininteractable>(); }); // find all mannequin components const mannequins = object findobjectsoftype\<mannequincomponent>(); mannequins foreach(m => { // enter the collider m onactive addlistener(contents => { mannequin openui(contents); const zepetocontext = zepetoplayers instance localplayer zepetoplayer character context; this previewer = new mannequinpreviewer(zepetocontext, contents); this previewer previewcontents(); }); // exit the collider m oncancel addlistener(() => { mannequin closeui(); this previewer? resetcontents(); }); }); } } スクリプトをオブジェクトに追加し、\[▶︎(play)]ボタンを押して実行します。 step 3 3 zepeto template type zepeto idを入力したら、そのアバターが着用しているコーデをマネキンが着用し登場します。ただし、自分で作ったオリジナルアイテムでない場合はコーデは確認できないです。 empty objectを生成して、名前をmannequinで作成します。 オブジェクトにインタラクションする為、colliderを必ず追加しなければなりません。 is tringeerチェックは必須です。万が一チェックが確認できなければ、自動的にトリッカーとして認識処理されます。 オブジェクトにマネキンコンポーネントを追加します。 please set the following in the inspector icon 基本的にはハンガーアイコンです。カスタムアイコンに変更可能です。 icon position 基本的にマネキンコンポーネントがあるオブジェクトの位置にアイコンが現れます。 position objectを追加後、値を入れると思い通りの位置に現れます。 zepeto id zepeto idを入力してください。 pose ポーズを選択し、設置できます。 今後、思い通りのポーズをポーズid形態に提供する予定です。 👍 シーンにマネキン スクリプトが既にある場合は、後の手順を省略できます。 empty objectを追加し、オブジェクトの名前をmannequinscriptで作成します。 zepeto> typescriptを追加し、スクリプトの名前をmannequinscriptで作成します。 以下内容通りに作成します。 import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, spawninfo } from 'zepeto character controller'; import { worldservice } from 'zepeto world'; import { itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { object } from 'unityengine'; export default class mannequinscript extends zepetoscriptbehaviour { private previewer mannequinpreviewer; start() { // code that creates a zepeto character based on the logged in id // zepetoplayers instance createplayerwithuserid(worldservice userid, spawninfo default, true); zepetoplayers instance onaddedlocalplayer addlistener(() => { const character = zepetoplayers instance localplayer zepetoplayer character; // add mannequin interactable component character gameobject addcomponent\<mannequininteractable>(); }); // find all mannequin components const mannequins = object findobjectsoftype\<mannequincomponent>(); mannequins foreach(m => { // enter the collider m onactive addlistener(contents => { mannequin openui(contents); const zepetocontext = zepetoplayers instance localplayer zepetoplayer character context; this previewer = new mannequinpreviewer(zepetocontext, contents); this previewer previewcontents(); }); // exit the collider m oncancel addlistener(() => { mannequin closeui(); this previewer? resetcontents(); }); }); } } スクリプトをオブジェクトに追加し、\[▶︎(play)]ボタンを押して実行します。 step 4 マネキン販売を使用 マネキン設置を正しく終えたら、マネキンオブジェクトのcollider領域内に入った際にインタラクション出来るアイコンが現れます。 アイコンをクリックした場合、購入ポップアップが開き、マネキンに設置したアイテムが現れます。 アイテムをクリックしたら、着せ替えが出来ます。 既に保有したアイテムは、価格の代わりにチェック表示が現れます。 マネキンオブジェクトのcollider 領域から出た際、着せ替えが消去され、本来のコーデに戻ります。 テスト環境 購入処理 unity editorで購入 プレイ中に購入が成功しコーデが変更できますが、プレイ終了すると本来の状態に戻ります。 qrコードモバイルテスト環境での購入 この場合は、実際のサーバーに接続してテストをするので、購入成功時、コーデが変更となりワールドを退出した際にも変更されたコーデが維持されます。 また、購入成功時に、実際のサーバーでアイテムを購入することになりますので、実際のアカウントが保有するジェムが差し引かれ、衣装アイテムを所持することになります。購入した衣装を任意に捨てたり削除することはできませんので、テスト時に慎重に購入してください。 zepeto studioにパッケージ登録後、テストリンクから接続して購入 この場合は、実際のサーバーに接続してテストをするので、購入成功時、コーデが変更となりワールドを退出した際にも変更されたコーデが維持されます。 また、購入成功時に、実際のサーバーでアイテムを購入することになりますので、実際のアカウントが保有するジェムが差し引かれ、衣装アイテムを所持することになります。購入した衣装を任意に捨てたり削除することはできませんので、テスト時に慎重に購入してください。 step 5 マネキンマルチプレイの同期化 マルチプレイは、他のプレイヤーがマネキン機能を使用して着用した衣装を同期させる必要があります。 👍 以下の内容を作業する前に、基本的なマルチプレイセッティングを完了させてください。 クライアントスクリプトを追加します。zepeto > typescript を追加して、スクリプトの名前をmannequincontrollerに設定します。 以下の内容通りに作成してください。 import { zepetoscriptbehaviour } from 'zepeto script'; import { room } from 'zepeto multiplay'; import { clothespreviewer, itemcontent, itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { zepetocontext } from 'zepeto'; import { zepetoplayer, zepetoplayers } from 'zepeto character controller'; import { gameobject, object, canvas, waitforsecondsrealtime, layermask } from 'unityengine'; import { player } from 'zepeto multiplay schema'; import { zepetoworldmultiplay } from 'zepeto world'; class characteritem { property string; id string; } class changeditem { sessionid string; characteritems characteritem\[]; } export default class mannequincontroller extends zepetoscriptbehaviour { private message type = { onchangeditem "onchangeditem", syncchangeditem "syncchangeditem", checkchangeditem "checkchangeditem" } private multiplay zepetoworldmultiplay; private room room; private previewer mannequinpreviewer private context zepetocontext private userzepetocontexts map\<string, zepetocontext> = new map\<string, zepetocontext>(); private currentmannequincomponent mannequincomponent = null; private selectmannequincomponent mannequincomponent = null; private isopenmannequinui boolean = false; private basicclothstring = "basiccloth" as const; start() { this multiplay = object findobjectoftype\<zepetoworldmultiplay>(); mannequin onopenedshopui addlistener((item) => { // when you click openshopbutton this openedshopui(item) }); mannequin onclosedshopui addlistener(() => { // when you click closeshopbutton this closedshopui() }); mannequin onselecteditem addlistener((itemcontent itemcontent, select boolean) => { // action when item is selected }); zepetoplayers instance onaddedlocalplayer addlistener(() => { const myplayer = zepetoplayers instance localplayer zepetoplayer; // mannequin const character = myplayer character; character gameobject addcomponent\<mannequininteractable>(); console log("local context set"); this context = character context; const mannequins = object findobjectsoftype\<mannequincomponent>() mannequins foreach(mannequin => { // when you enter mannequin collider mannequin onactive addlistener(contents => { if (this currentmannequincomponent != null && this currentmannequincomponent == mannequin) { return; } if (contents == null || contents length == 0) { console log("no mannequin data"); return; } if (this isopenmannequinui) { this breakmannequin(); } this selectmannequincomponent = mannequin; mannequin openui(contents); console log("onactive"); }); // \[option] when you leave mannequin collider mannequin oncancel addlistener( () => { if (this currentmannequincomponent == null || this currentmannequincomponent != mannequin) { return; } this breakmannequin(); console log("oncancel"); }); let iconcanvas = mannequin gameobject getcomponentinchildren\<canvas>(true); if (iconcanvas != null) { iconcanvas gameobject layer = layermask nametolayer("ui"); } }); }); zepetoplayers instance onaddedplayer addlistener((sessionid string) => { if (zepetoplayers instance getplayer(sessionid) islocalplayer) { return; } const usercontext = zepetoplayers instance getplayer(sessionid) character context; this userzepetocontexts set(sessionid, usercontext); this room send(this message type checkchangeditem, sessionid); }); this multiplay roomjoined += (room room) => { this room = room; this addmessagehandler(); }; } private addmessagehandler() { // \[option] synchronize each player's clothes this room addmessagehandler\<changeditem>(this message type syncchangeditem, message => { console log("syncchangeditem"); if (message == null) { return; } if (false == this userzepetocontexts has(message sessionid)) { return; } let itemcontents itemcontent\[] = \[]; for (const characteritem of message characteritems) { let itemcontent itemcontent = new itemcontent(); itemcontent id = characteritem id; itemcontent property = parseint(characteritem property); if (itemcontent id == this basicclothstring) { itemcontent id = ""; } itemcontents push(itemcontent); } let clothespreviewer\ clothespreviewer = new clothespreviewer(this userzepetocontexts get(message sessionid),itemcontents); clothespreviewer previewcontents(); }); } private closedshopui() { this currentmannequincomponent = null; this isopenmannequinui = false; } private openedshopui(items itemcontent\[]) { this isopenmannequinui = true; this currentmannequincomponent = this selectmannequincomponent; this previewer = new mannequinpreviewer(this context, items); this previewer onchanged addlistener((changevalues) => { let characteritems characteritem\[] = \[]; for (const changevalue of changevalues) { let characteritem characteritem = new characteritem(); characteritem id = changevalue id; characteritem property = changevalue property tostring(); if (characteritem id == "") { characteritem id = this basicclothstring; } characteritems push(characteritem); } this room send(this message type onchangeditem, characteritems); }); this previewer previewcontents(); } public breakmannequin() { mannequin closeui(); if (this previewer) { this previewer resetcontents(); this previewer = null; } this currentmannequincomponent = null; this isopenmannequinui = false; } } hierarchy > empty objectを作成して、名前をmannequincontrollerに設定します。 先ほど作成したスクリプトをmannequincontrollerに追加してください。 次に、world multiplay > index tsで、以下の内容を参考にサーバーコードを作成してください。 import { sandbox, sandboxoptions, sandboxplayer } from 'zepeto multiplay'; import { player, transform, vector3 } from 'zepeto multiplay schema'; class characteritem { property string; id string; } class changeditem { sessionid string; characteritems characteritem\[]; } enum cloth { top = "19", bottom = "20" , dress = "22" } export default class extends sandbox { private message type = { onchangeditem "onchangeditem", syncchangeditem "syncchangeditem", checkchangeditem "checkchangeditem" } // map\<sessionid, map\<characteritem property, characteritem id>> private changeditems map\<string, map\<string, string>>; oncreate(options sandboxoptions) { // called when the room object is created // handle the state or data initialization of the room object this onmessage("onchangedtransform", (client, message) => { const player = this state players get(client sessionid); const transform = new transform(); transform position = new vector3(); transform position x = message position x; transform position y = message position y; transform position z = message position z; transform rotation = new vector3(); transform rotation x = message rotation x; transform rotation y = message rotation y; transform rotation z = message rotation z; player transform = transform; }); this onmessage("onchangedstate", (client, message) => { const player = this state players get(client sessionid); player state = message state; player substate = message substate; // character controller v2 }); // mannequin server code this changeditems = new map\<string, map\<string, string>>(); this onmessage\<characteritem\[]>(this message type onchangeditem, (client, message) => { // overwrite clothes and set new parts if (this changeditems has(client userid)) { const changeditemmap = this changeditems get(client userid); for (const characteritem of message) { if (characteritem property == cloth dress) { // in the case of a dress (22), remove the top (19) and bottom (20) if (changeditemmap has(cloth top)) { changeditemmap delete(cloth top); } if (changeditemmap has(cloth bottom)) { changeditemmap delete(cloth bottom); } } else if (characteritem property == cloth top || characteritem property == cloth bottom) { // remove the dress if it is a top (19) or bottom (20) if (changeditemmap has(cloth dress)) { changeditemmap delete(cloth dress); } } changeditemmap set(characteritem property,characteritem id); console log(`onchangeditem old ${client userid} ${characteritem property} // ${characteritem id}`); } } else { // initial registration let changeditemmap map\<string,string> = new map\<string, string>(); for (const characteritem of message) { changeditemmap set(characteritem property,characteritem id); } this changeditems set(client sessionid,changeditemmap); } let changeditem changeditem = new changeditem(); changeditem sessionid = client sessionid; changeditem characteritems = message; console log(`onchangeditem ${changeditem sessionid}`); for (const characteritem of changeditem characteritems) { console log(` ${characteritem property} ${characteritem id} `); } this broadcast(this message type syncchangeditem, changeditem, {except client}); }); this onmessage\<string>(this message type checkchangeditem,(client, message) => { if (false == this changeditems has(message)) { return; } let changeditem changeditem = new changeditem(); changeditem sessionid = client sessionid; changeditem characteritems = \[]; for (const property of this changeditems get(message) keys()) { let characteritem characteritem = new characteritem(); characteritem property = property; characteritem id = this changeditems get(message) get(property); changeditem characteritems push(characteritem); } client send\<changeditem>(this message type syncchangeditem, changeditem ); }); } onjoin(client sandboxplayer) { // create the player object defined in schemas json and set the initial value console log(`\[onjoin] sessionid ${client sessionid}, hashcode ${client hashcode}, userid ${client userid}`) const player = new player(); player sessionid = client sessionid; if (client userid) { player zepetouserid = client userid; } // manage the player object using sessionid, a unique key value of the client object // the client can check the information about the player object added by set by adding the add onadd event to the players object this state players set(client sessionid, player); } async onleave(client sandboxplayer, consented? boolean) { // by setting allowreconnection, it is possible to maintain connection for the circuit, but clean up immediately in the basic guide // the client can check the information about the deleted player object by adding the add onremove event to the players object this state players delete(client sessionid); if (this changeditems has(client sessionid)) { this changeditems delete(client sessionid); } } } マルチプレイサーバーをonにし、テストをしてください。 イベント関数 zepeto mannequin 1 1 0 バージョンからイベント関数を使用することができます。 zepeto mannequin mannequin 関数 説明 public static onselecteditem unityengine events unityevent$2\<itemcontent, boolean>; マネキンコーデ購入ウィンドウで特定のアイテムを選択したときに呼び出され、選択したアイテム情報アイテムcontentおよびアイテム選択の可否がboolean値に移されます。 public static onsucceededpurchaseitems unityengine events unityevent$1\<itemcontent\[]>; コーデ購入完了時に呼び出され、購入したアイテム情報アイテムcontentリストが渡されます。 public static onfailedpurchaseitems unityengine events unityevent$1\<itemcontent\[]>; コーデの購入に失敗時に呼び出され、購入に失敗したアイテム情報アイテムcontentリストが渡されます。 public static onapplieditems unityengine events unityevent$1\<itemcontent\[]>; コーデ購入成功後、購入したアイテムを着用すると選択した時に呼び出され、着用したアイテム情報アイテムcontentリストが渡されます。 public static onopenedshopui unityengine events unityevent$1\<itemcontent\[]>; マネキンコーデ購入ウィンドウが開く時に呼び出され、購入ウィンドウにあるアイテム情報itemcontentリストが送信されます。 public static onclosedshopui unityengine events unityevent; マネキンコーデの購入ウィンドウが閉じたときに呼び出されます。 zepeto mannequin basepreviewer 関数 説明 public onchanged unityengine events unityevent$1\<zepeto mannequin basepreviewer changedvalue\[]>; マネキンを押して着用中のアイテム情報が変わると毎回呼び出され、onchagedvalueリストが移動します。 onchagedvalueクラスのメンバー変数情報は次のとおりです。 public property zepetopropertyflag コーデ詳細情報 public id string item id mannequin worldcamera mannequin worldcameraをセッティングしないと、マネキンインタラクションアイコンと関連したカメラとしてdepthが低いカメラが設定されます。 他のカメラを別途セッティングしたい場合には、mannequin worldcameraを使用すると指定できます。 variable 説明 mannequin worldcamera マネキンインタラクションアイコンと関連したカメラを直接セッティングできるようにする変数です。 従来のmannequinscriptにmannequin worldcameraをセッティングした例は以下の通りです。 import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, spawninfo } from 'zepeto character controller'; import { worldservice } from 'zepeto world'; import { itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { object } from 'unityengine'; export default class mannequinscript extends zepetoscriptbehaviour { private previewer mannequinpreviewer; start() { // code that creates a zepeto character based on the logged in id // zepetoplayers instance createplayerwithuserid(worldservice userid, spawninfo default, true); zepetoplayers instance onaddedlocalplayer addlistener(() => { const character = zepetoplayers instance localplayer zepetoplayer character; // add mannequin interactable component character gameobject addcomponent\<mannequininteractable>(); // code to set up the mannequin icon world canvas camera as the zepeto camera mannequin worldcamera = zepetoplayers instance localplayer zepetocamera camera; }); // find all mannequin components const mannequins = object findobjectsoftype\<mannequincomponent>(); mannequins foreach(m => { // enter the collider m onactive addlistener(contents => { mannequin openui(contents); const zepetocontext = zepetoplayers instance localplayer zepetoplayer character context; this previewer = new mannequinpreviewer(zepetocontext, contents); this previewer previewcontents(); }); // exit the collider m oncancel addlistener(() => { mannequin closeui(); this previewer? resetcontents(); }); }); } } oncontentsloaded api a callback has been added that is triggered when all clothing items are fully loaded while using the mannequin api to equip clothing items public oncontentsloaded unityengine events unityevent$1\<zepeto mannequin basepreviewer changedvalue\[]>; an example of setting oncontentsloaded in the existing mannequinscript is as follows import { zepetoscriptbehaviour } from 'zepeto script'; import { zepetoplayers, spawninfo } from 'zepeto character controller'; import { worldservice } from 'zepeto world'; import { itemcontentsrequest, mannequin, mannequincomponent, mannequininteractable, mannequinpreviewer } from 'zepeto mannequin'; import { object } from 'unityengine'; export default class mannequinscript extends zepetoscriptbehaviour { private previewer mannequinpreviewer; start() { // code that creates a zepeto character based on the logged in id // zepetoplayers instance createplayerwithuserid(worldservice userid, spawninfo default, true); zepetoplayers instance onaddedlocalplayer addlistener(() => { const character = zepetoplayers instance localplayer zepetoplayer character; // add mannequin interactable component character gameobject addcomponent\<mannequininteractable>(); }); // find all mannequin components const mannequins = object findobjectsoftype\<mannequincomponent>(); mannequins foreach(m => { // enter the collider m onactive addlistener(contents => { mannequin openui(contents); const zepetocontext = zepetoplayers instance localplayer zepetoplayer character context; this previewer = new mannequinpreviewer(zepetocontext, contents); // adding listener to oncontentsloaded event this previewer oncontentsloaded addlistener((changedvalues)=>{ console log('contents loaded ', changedvalues); }); this previewer previewcontents(); }); // exit the collider m oncancel addlistener(() => { mannequin closeui(); this previewer? resetcontents(); }); }); } }