プレイヤーがリアルタイムでアバターのボディを作成、カスタマイズ、購入できるエクスペリエンスを公開できます。購入されたこれらのカスタムボディは、プレイヤーのRobloxインベントリに直接保存され、プレイヤーは他のエクスペリエンスでカスタムアバターを装備して着用できます。
エクスペリエンスの所有者がエクスペリエンス内アバター作成を実装すると、アバターアイテムのクリエイターおよびエクスペリエンス所有者の両方としてマーケットプレイスの手数料の恩恵を受けます。エクスペリエンス内で作成されたアセットが検査されると、そのアイテムは元のエクスペリエンスへのリンクを提供します。
Robloxのアバタークリエーターデモでエクスペリエンス内作成をテストできます。
エクスペリエンス内作成を実装する方法
以下の指示とコードリファレンスを使用して、最初のエクスペリエンス内アバター作成プロジェクトを作成します。以下の指示では、プレイヤーが公開前に変更およびカスタマイズできる基本ボディModelを使用します。
始める前に、以下の内容を理解しておいてください。
- アバターモデル — 次の実装には、Robloxの15部品仕様を満たす基本ボディのインポートが必要です。このModelは、ユーザーの追加のカスタマイズと変更の基盤として機能します。
- 基本ボディは、Robloxのアバターボディガイドラインを満たす必要があります。これには、顔のリギング用のFACSコントロールの最小数が含まれます。
- アバター作成トークン — アバター作成を実装するエクスペリエンスには、少なくとも1つの作成トークンが必要です。これらのトークンは、購入するためにRobuxを必要とし、エクスペリエンス内での購入のための価格やその他の販売設定を設定することができます。
- APIクラス
- AvatarCreationService — アバター作成プロンプトとバリデーションを処理します。
- EditableImage — テクスチャのランタイム作成と操作を処理します。
- EditableMesh — メッシュジオメトリのランタイム操作を処理します。
- WrapDeformer — アバターキャラクターが3D衣服を装備できるようにするための見えない外部ケージジオメトリのランタイム操作を処理します。
基本ボディをインポートする
基本ボディは、ユーザーがカスタマイズおよび編集できる初期の基盤として機能します。自分のModelを使用することも、インポーターを使用してカスタムアセットをインポートし、アバター設定で設定することもできます。
基本ボディはRobloxのアバター仕様に従わなければならず、頭部、胴体、左腕、左脚、右腕、右脚の6部位を構成する15のMeshPartインスタンスおよびその他のアバターコンポーネントを含む必要があります。
正しく設定されたアバターボディのリファレンスやサンプルについては、アバターリファレンスを参照してください。
編集APIを実装する
ユーザーがエクスペリエンス内でアバターのMeshPartインスタンスを編集できるシステムを開発するには、テクスチャ編集にEditableImage、メッシュ編集にEditableMesh、メッシュ編集中にスキニングおよびFACSデータを維持するにはWrapDeformerを使用します。
基本ボディをインポートした後、次のスクリプトを使用してEditableImages、EditableMeshes、およびWrapDeformersを設定します。
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- MeshPartにWrapDeformerを作成してアタッチlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- WrapTargetのケージメッシュ用の編集可能なメッシュを作成local cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- WrapDeformerにケージメッシュを割り当てwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- 元のMeshPartから編集可能なメッシュを作成local editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- 編集可能なメッシュから新しいMeshPartを生成local newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- 元のMeshPartからサイズ、位置、テクスチャをコピーnewMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- 新しいMeshPartを元のMeshPartに適用meshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- このTextureIDに対してすでにEditableImageが存在する場合は、新しいものを作成するのではなく再利用if textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- 新しいEditableImageを作成し、テクスチャコンテンツとして適用local editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(meshPart.TextureID))textureIdToEditableImageMap[meshPart.TextureID] = editableImagemeshPart.TextureContent = Content.fromObject(editableImage)endlocal function setupModel(model)-- テクスチャIDによってEditableImageインスタンスを再利用するためのマップlocal textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- WrapTargetの有無に応じてMeshPartを設定-- WrapTargetが存在する場合、EditableMeshを持つWrapDeformerの子を追加-- そうでなければ、直接MeshPartにEditableMeshを適用local wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- MeshPartのためのEditableImageを設定setupMeshTexture(descendant, textureIdToEditableImageMap)endendプレイヤーが基本ボディに色を付けたり、描画したり、ステッカーを追加したりできるようにするEditableImageツールを作成します。DrawImage()、DrawRectangle()、WritePixelsBuffer()などのAPIを活用できます。
高度な変形のために、DrawImageTransformed()を使用すると、1つのEditableImageを別のEditableImageに描画する際の位置、回転、スケールを指定できます。同様に、DrawImageProjected()はDrawImage()と非常に似ていますが、描画画像を正しく投影します。
local function recolorTexture(meshPart: MeshPart,color: Color3)local bodyPartTexture = AssetService:CreateEditableImageAsync(meshPart.TextureID)meshPart.TextureContent = Content.fromObject(bodyPartTexture)bodyPartTexture:DrawRectangle(Vector2.new(0, 0),bodyPartTexture.Size,color,0,Enum.ImageCombineType.Overwrite)endlocal function applySticker(meshPart: MeshPart,textureCoordinate: Vector2,stickerId: TextureId)local bodyPartTexture = AssetService:CreateEditableImageAsync(meshPart.TextureID)meshPart.TextureContent = Content.fromObject(bodyPartTexture)local stickerTexture = AssetService:CreateEditableImageAsync(stickerId)bodyPartTexture:DrawImage(textureCoordinate, stickerTexture, Enum.ImageCombineType.BlendSourceOver)endlocal function applyStickerProjected(meshPart: MeshPart,targetMesh: EditableMesh,stickerId: TextureId,raycastHitPos: Vector3)local bodyPartTexture = AssetService:CreateEditableImageAsync(meshPart.TextureID)local relativePos = meshPart.CFrame:PointToWorldSpace(raycastHitPos)local direction = (workspace.CurrentCamera.CFrame.Position - relativePos).Unitlocal projectionParams: ProjectionParams = {Direction = meshPart.CFrame:VectorToObjectSpace(direction),Position = meshPart.CFrame:PointToObjectSpace(relativePos),Size = Vector3.new(1, 1, 1),Up = meshPart.CFrame:VectorToObjectSpace(Vector3.new(0, 1, 0)),}local stickerTexture = AssetService:CreateEditableImageAsync(stickerId)local localBrushConfig: BrushConfig = {Decal = stickerTexture,ColorBlendType = Enum.ImageCombineType.BlendSourceOver,AlphaBlendType = Enum.ImageAlphaType.Default,BlendIntensity = 1,FadeAngle = 90.0}bodyPartTexture:DrawImageProjected(targetMesh, projectionParams, localBrushConfig)end
WrapDeformerとEditableMeshを使用して、ボディのメッシュ変形を編集するためのツールを作成します。
WrapDeformerは、スキニングおよびFACSデータを維持しながら、レンダリングされたMeshPartジオメトリのライブ変形を処理します。
EditableMeshを使用して、WrapDeformerが応答するケージメッシュを編集できます。
local function deformBodyPart(meshPart: MeshPart,controlPointCenter: Vector3,controlPointRadius: number,controlPointDeformation: Vector3)local wrapTarget = meshPart:FindFirstChildWhichIsA("WrapTarget")local cageMeshId = wrapTarget.CageMeshIdlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPartlocal cageEditableMesh = AssetService:CreateEditableMeshAsync(cageMeshId)local verticesWithinSphere =cageEditableMesh:FindVerticesWithinSphere(controlPointCenter, controlPointRadius)for _, vertexId in verticesWithinSphere dolocal vertexPosition = cageEditableMesh:GetPosition(vertexId)cageEditableMesh:SetPosition(vertexId, vertexPosition + controlPointDeformation)endwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))end
作成プロンプトを作成する
基本ボディと編集APIの設定が完了したら、AvatarCreationService:PromptCreateAvatarAsync()を使用して、ユーザーがエクスペリエンスから作成して購入できるようにするプロンプトを作成します。
export type BodyPartInfo = {
bodyPart: Enum.BodyPart,
instance: Instance --作成されたMeshPartsを含むフォルダ
}
export type BodyPartList = {BodyPartInfo}
local function publishAvatar(bodyPartInstances: BodyPartList, player: Player, tokenId: string)
local humanoidDescription = Instance.new("HumanoidDescription")
for _, bodyPartInfo in bodyPartInstances do
local bodyPartDescription = Instance.new("BodyPartDescription")
bodyPartDescription.Instance = bodyPartInfo.instance
bodyPartDescription.BodyPart = bodyPartInfo.bodyPart
bodyPartDescription.Parent = humanoidDescription
end
local success, result, bundleIdOrErrorMessage, outfitId = pcall(function()
return AvatarCreationService:PromptCreateAvatarAsync(tokenId, player, humanoidDescription)
end)
if success then
if result == Enum.PromptCreateAvatarResult.Success then
print("BundleIdで正常にアップロードされました: ", bundleIdOrErrorMessage)
print("OutfitIdで正常にアップロードされました: ", outfitId)
else
print("エラーメッセージとともにアップロードに失敗しました:", bundleIdOrErrorMessage)
end
else
print("アバター作成に失敗しました")
end
endAvatarCreationService:PromptCreateAvatarAsync()は、購入または作成のためのアバターを表すHumanoidDescriptionパラメータを受け取ります。アバター作成の場合、キャラクターのHumanoidDescriptionには、6つのボディパーツそれぞれのために作成される新しいアセットが含まれている必要があります (Head, Torso, RightLeg, LeftLeg, RightArm, LeftArm). オプションで、新しいHairアクセサリーも含めることができます。
これをサポートするために、HumanoidDescriptionには6つのBodyPartDescription子が含まれている必要があります。各BodyPartDescription.Instanceプロパティは、ボディパーツを構成するすべてのMeshPartインスタンスを含むFolderを参照します。例えば、LeftArmフォルダには LeftHand、LeftUpperArm、およびLeftLowerArm MeshPartsが含まれます。 BodyPartDescription.BodyPartプロパティも、関連するEnum.BodyPartに設定される必要があります。
15のMeshPartボディパーツそれぞれには以下が含まれる必要があります:
提供されたHumanoidDescriptionには、意図した作成に対してボディパーツやアクセサリーを表す既存のアセットIDは含めないでください。一方で、HumanoidDescriptionにはBodyTypeScale、HeadScale、HeightScale、WidthScale、および ProportionScaleのヒューマノイドスケールを含めることができます。基本ボディがインポートされる際のスケールが、HumanoidDescriptionに提供されるスケールと一致することを確認してください。

アクセサリーを含める
髪の毛などのアクセサリーを含める場合、HumanoidDescriptionには子AccessoryDescriptionが含まれている必要があります。そこでは:
- AccessoryDescription.Instanceプロパティが Accessoryインスタンスを参照します。
- AccessoryDescription.AccessoryTypeプロパティが 関連するEnum.AccessoryTypeに設定されます。
- あなたの作成にEnum.AccessoryType.Hairを含める場合、MeshPartにはEditableImageが含まれている必要があります。ただし、WrapDeformerの子は含まれず、EditableMeshが直接MeshPartに設定される必要があります。
アバター作成トークンを生成する
AvatarCreationService:PromptCreateAvatarAsync()は、アバター作成トークンIDパラメータを受け取ります。このトークンは、あなたのユニバースからの作成のキーであり、エクスペリエンスからのアバター作成の価格を設定するために使用できるものです。トークンの生成に関する指示および追加の詳細については、アバター作成トークンを参照してください。
トークンを購入して生成した後、クリエイターハブでトークンを検査し、AvatarCreationService:PromptCreateAvatarAsync()APIに使用できるIDを見つけます。

出所に基づいてプレイヤーが参加することに応じて処理する
エクスペリエンス内で作成されたアバターバンドルには、アバターが作成された元のエクスペリエンスへの帰属リンクが含まれています。アバターが他のプレイヤーによって検査されると、そのアバターが作成されたエクスペリエンスに訪問するオプションを表示するプロンプトが表示されます。

この帰属リンクを使用してプレイヤーがあなたのエクスペリエンスに参加することに対応するには、Player:GetJoinData()を使用し、返されたテーブルをGameJoinContextとして解析します。
GameJoinContextには次のテーブルの値が含まれています。
- JoinSource — Enum.JoinSource
- この帰属リンクからあなたのエクスペリエンスに参加するPlayerは、作成されたアイテムからのエントリーを示すためにEnum.JoinSource.CreatedItemAttributionになります。
- ItemType — オプショナル Enum.AvatarItemType
- AssetId — オプショナル string
- OutfitId — オプショナル string
- AssetType — オプショナル Enum.AssetType