プレイヤーがリアルタイムでアバターボディを作成、カスタマイズ、購入できる体験を公開できます。購入すると、これらのカスタムボディはプレイヤーの Roblox インベントリに直接持ち物リストされ、プレイヤーが他のエクスペリエンスでカスタムアバターを装備して着用できるようにします。
経験中のアバター作成を実装する経験所有者は、マーケットプレイスの手数料 を両方、 アバターアイテムのクリエーター と 経験所有者 の両方から受け取ります。エクスペリエンス内で作成されたアセットが調査されると、アイテムは作成されたオリジナルのエクスペリエンスへのリンクを提供します。
RobloRoblox(ロブロックス) の アバタークリエーター デモで、体験中の作成をテストできます。
経験中の作作品を実装する方法
次の指示とコード参照を使用して、最初のバーチャル空間アバター作成プロジェクトを作成します。次の指示では、プレイヤーが公開する前に変更してカスタマイズできるベースボディ Model を使用します。
開始する前に、フォロー中のことを理解してください:
- アバターモデル — 次の実装では、Roblox の 15部分仕様に対応するベースボディをインポートする必要があります。この Model は、追加のユーザカスタマイズと修正の基礎として機能します。
- ベースボディは、RRoblox(ロブロックス)blox のアバターボディガイドライン に含まれる、顔のリギング用の FACS コントロールの最小数を満たさなければなりません。
- アバター作成トークン — アバター作成を実装するエクスペリエンスには、少なくとも 1つの作成トークンが必要です。これらのトークンは、Robux を購入して、体験内で行われた購入の価格とその他の販売設定を設定できるようにする必要があります。
- APIクラス
- AvatarCreationService — アバター作成の促進と検証を処理します。
- EditableImage — テクスチャの作成と操作を処理します。
- EditableMesh — メッシュジオメトリのランタイム操作を処理します。
- WrapDeformer — アバターキャラクターが3D衣装を装備できるようにする透明な外部ケージの幾何操作のランタイム処理。
基本ボディをインポートする
ベースボディは、ユーザーがカスタマイズして編集できる最初の基礎として機能します。自分の Model を使用したり、3Dインポーター でカスタムアセットをインポートして、アバター設定 を通じて設定できます。
ベースボディは、Roblox のアバター仕様に従わなければならず、6つのボディパーツ(頭、胴体、左腕、左脚、右腕、右脚)と他の アバターコンポーネント を構成する15のインスタンスなどのコンポーネントを含まなければなりません。
正しく構成されたアバターボディの参照とサンプルについては、アバター参照 を参照してください。
編集 API を実装する
エクスペリエンス内のアバターでユーザーが編集できる MeshPart インスタンスを開作品し、テクスチャ編集のために EditableImage 、メッシュ編集のために EditableMesh 、メッシュ編集中にスキンと FACS データを維持するために WrapDeformer を使用します。
ベースボディをインポートした後、次のスクリプトを使用して EditableImages、EditableMeshes、および WrapDeformers を設定します。
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- メッシュパーツにラップディフォーマーを作成して付属するlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- 包装ターゲットのメッシュを編集可能にするメッシュを作成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,})-- 編集可能なメッシュから新しいメッシュパーツを生成local newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- オリジナルのメッシュパーツからサイズ、位置、テクスチャをコピーnewMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- 新しいメッシュパーツを元に戻すmeshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- このテクスチャID に既に編集可能な画像が存在する場合、新しいものを作成するのではなく、それを再使用しますif textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- 新しい編集可能な画像を作成し、テクスチャコンテンツとして適用する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 の存在に基づいてメッシュパーツを構成する-- WrapTarget が存在する場合、編集可能なメッシュを持つ WrapDeformer の子を追加します-- そうでない場合は、MeshPart に直接 EditableMesh を適用しますlocal wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- メッシュパーツの編集可能な画像を構成するsetupMeshTexture(descendant, textureIdToEditableImageMap)endendプレイヤーがベースボディに色を塗り加えたり、ステッカーを描画したり、追加したりできる EditableImageAPI を利用できるようになります DrawImage() , DrawRectangle() , WritePixelsBuffer() のように。
高度な変換では、DrawImageTransformed() で 1つの編集可能な画像をもう1つに描画するときに、位置、回転、スケールを指定できます。同様に、DrawImageProjected() は、DrawImage() と同じように機能しますが、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 = (game.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 に応答するケージメッシュを変更できます。
- 使用 WrapDeformer:SetCageMeshContent() 関連するケージメッシュを表すインスタンスを 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 --作成されたメッシュパーツがあるフォルダ
}
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("Successfully uploaded with BundleId: ", bundleIdOrErrorMessage)
print("Successfully uploaded with OutfitId: ", outfitId)
else
print("Unsuccessfully uploaded with error message:", bundleIdOrErrorMessage)
end
else
print("Avatar creation unsuccessful")
end
end
AvatarCreationService:PromptCreateAvatarAsync() は、購入または作作品の意図されたアバターを表現するための HumanoidDescription パラメータを取ります。アバターの作作品には、キャラクターの HumanoidDescription には、6つの身体部分 ( Head , Torso , RightLeg , LeftLeg , RightArm , LeftArm ) のそれぞれに作成される新しいアセットが含まれている必要があります。オプションで、新しい Hair アクセサリーも含めることができます。
これをサポートするために、HumanoidDescription は 6 BodyPartDescription の子供を含める必要があります。それぞれの BodyPartDescription.Instance プロパティは、体の部分を構成するすべての Folder インスタンスを含む MeshPart を参照します。たとえば、 フォルダには , , そして が含まれています。BodyPartDescription.BodyPart プロパティも、関連する Enum.BodyPart に設定する必要があります。
15の各MeshPartボディパーツは次のものを含まなければなりません:
- An EditableImage .
- A WrapDeformer に EditableMesh がある。
提供された HumanoidDescription は、意図した作作品に体部またはアクセサリを表現するための既存のアセットIDを含まないでしょう。一方、HumanoidDescription には、BodyTypeScale 、HeadScale 、HeightScale 、WidthScale 、およびProportionScaleが含まれる可能性があります。ベースボディがインポートされるスケールを注意して、HumanoidDescription に提供されたスケールと一致するようにしてください。

アクセサリを含む
アクセサリーを含める場合、髪など、 には、次のような子を含める必要があります:
- The AccessoryDescription.Instance プロパティは、Accessory インスタンスを参照します。
- AccessoryDescription.AccessoryType プロパティは、関連する Enum.AccessoryType に設定されます。
- 作品に Enum.AccessoryType.Hair を含める場合、MeshPart には EditableImage が含まれる必要があります。しかし、子供の を含めるべきではなく、直接 に設定を含めるべきです。
アバター作成トークンを生成する
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