プレイヤーがリアルタイムでアバターのボディを作成、カスタマイズ、購入できるエクスペリエンスを公開できます。これは、エクスペリエンス内メイクアップ作成に加えて行えます。購入されたこれらのカスタムアイテムは、プレイヤーの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の子を追加する-- そうでない場合は、EditableMeshをMeshPartに直接適用する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()は、EditableImageインスタンスがMeshPartと一緒に使用される場合に、描画された画像を適切に投影します。
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は、レンダリングされたMeshPartジオメトリのライブ変形を処理し、基礎となるスキニングとFACSデータを維持します。
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の子は含めず、MeshPartに直接EditableMeshを含める必要があります。
アバター作成トークンを生成する
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