プレイヤーがリアルタイムでアバターのボディを作成、カスタマイズ、および購入できる経験を公開することができます。購入されたこれらのカスタムボディは、プレイヤーの Roblox インベントリに直接保存され、他の経験でカスタムアバターを装備して着用することができます。
経験の所有者は、経験内でアバター作成を実装することで、アバターアイテムの クリエイター と 経験所有者 の両方として マーケットプレイスの手数料 の恩恵を受けます。経験内で作成されたアセットが検査されると、そのアイテムはそれが作成された元の経験へのリンクを提供します。
Roblox の Avatar Creator デモで、経過内での作成をテストできます。
経験内での作成を実装する方法
以下の指示とコードリファレンスを使用して、あなたの最初の経験内アバター作成プロジェクトを作成します。次の指示では、プレイヤーが公開前に修正とカスタマイズができる基本的なボディ Model を使用します。
始める前に、以下の内容をよく理解しておいてください。
- アバターモデル — 次の実装では、Roblox の 15 パーツの仕様に従った基本ボディをインポートする必要があります。この Model は追加のユーザーによるカスタマイズや修正の基礎として機能します。
- 基本ボディは、Roblox の アバターのボディガイドライン を満たす必要があり、顔のリギングのための FACS コントロールの最低数を含める必要があります。
- アバター作成トークン — アバター作成を実装する経験には、少なくとも 1 つの作成トークンが必要です。これらのトークンは Robux で購入する必要があり、経験内での購入に対して価格や他の販売設定を設定することができます。
- API クラス
- AvatarCreationService — アバター作成のプロンプトとバリデーションを処理します。
- EditableImage — テクスチャのランタイム作成と操作を処理します。
- EditableMesh — メッシュジオメトリのランタイム操作を処理します。
- WrapDeformer — アバターキャラクターが 3D 服 を装備できるようにするための不可視の外骨格ジオメトリのランタイム操作を処理します。
基本ボディをインポートする
基本ボディは、ユーザーがカスタマイズおよび編集できる初期の基盤として機能します。独自の Model を使用するか、Importerを使用してカスタムアセットをインポートし、Avatar Setup 経由で設定できます。
基本ボディは、Roblox のアバター仕様に従わなければならず、頭部、胴体、左腕、左脚、右腕、右脚の 6 つのボディパーツを構成する 15 の MeshPart インスタンスや、その他の アバターコンポーネント を含める必要があります。
適切に構成されたアバターボディのリファレンスやサンプルについては、Avatar referencesを参照してください。
編集 API を実装する
ユーザーがあなたの経験内のアバターの MeshPart インスタンスを編集できるシステムを開発するには、テクスチャ編集用に EditableImage、メッシュ編集用に EditableMesh、メッシュ編集中にスキニングと FACS データを維持するために WrapDeformer を使用します。
基本ボディをインポートした後、以下のスクリプトを使用して EditableImages、EditableMeshes、WrapDeformers を設定します。
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- Create and attach a WrapDeformer to the MeshPartlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- Create an editable mesh for the wrap target's cage meshlocal cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- Assign the cage mesh to the WrapDeformerwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- Create an editable mesh from the original MeshPartlocal editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- Generate a new MeshPart from the editable meshlocal newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- Copy size, position, and texture from the original MeshPartnewMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- Apply the new MeshPart back to the originalmeshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- If EditableImage already exists for this TextureID, reuse it rather than making a new oneif textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- Create a new EditableImage and apply it as the texture contentlocal editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(meshPart.TextureID))textureIdToEditableImageMap[meshPart.TextureID] = editableImagemeshPart.TextureContent = Content.fromObject(editableImage)endlocal function setupModel(model)-- Map for reusing EditableImage instances by texture IDlocal textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- Configure MeshPart based on WrapTarget presence-- If WrapTarget is present, add a WrapDeformer child with an EditableMesh-- Otherwise, apply EditableMesh to the MeshPart directlylocal wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- Configure the EditableImage for the MeshPartsetupMeshTexture(descendant, textureIdToEditableImageMap)endendEditableImage ツールを作成して、プレイヤーが基本ボディに色を付けたり、描画したり、ステッカーを追加できるようにします。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 --Folder with Created 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("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 プロパティは、ボディパーツを構成するすべての 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 パラメータを必要とします。このトークンは、あなたのユニバースからの創作のためのキーであり、経験からアバター作成の価格を設定するために使用できます。トークンを生成する手順や詳細については、アバター作成トークンを参照してください。
トークンを購入し生成した後、クリエイターハブでトークンを検査して ID を見つけ、その ID を AvatarCreationService:PromptCreateAvatarAsync() API に使用できます。

帰属によるプレイヤーの参加に応答する
経験内で作成されたアバターバンドルには、アバターが作成された元の経験への帰属リンクが含まれています。他のプレイヤーがアバターを検査した場合、そのアバターが作成された経験を訪れるオプションを提供するプロンプトが表示されます。

この帰属リンクを使用してあなたの経験に参加するプレイヤーを処理するには、Player:GetJoinData() を使用し、返されたテーブルを GameJoinContext 用にパースします。
GameJoinContext には以下のテーブル値が含まれています:
- JoinSource — Enum.JoinSource
- この帰属リンクからあなたの経験に参加する Player は、作成されたアイテムからの参加を示す Enum.JoinSource.CreatedItemAttribution になります。
- ItemType — オプションの Enum.AvatarItemType
- AssetId — オプションの string
- OutfitId — オプションの string
- AssetType — オプションの Enum.AssetType