您可以發布一個允許玩家實時創建、自訂和購買角色的體驗。當購買後,這些自訂角色會直接保存到玩家的 Roblox 清單中,允許玩家在其他體驗中裝備和穿戴這些自訂角色。
實施體驗內角色創建的體驗擁有者可以在作為角色物品的 創作者 和 體驗擁有者 之間受益於 市場手續費。如果體驗內創建的資產被檢查,該物品會提供一個鏈接,指向其創建的原始體驗。
您可以在 Roblox 的 角色創建者 演示中測試體驗內創建。
如何實施體驗內創建
使用以下說明和代碼參考來創建您的第一個體驗內角色創建項目。以下說明使用一個基礎身體 Model,玩家可以在發布前對其進行修改和自訂。
在開始之前,請熟悉以下內容:
- 角色創建令牌 — 實施角色創建的體驗需要至少一個創建令牌。這些令牌需要用 Robux 購買,並允許您設置價格和其他銷售設置以供消費者在體驗內進行購買。
- API 類
- AvatarCreationService — 處理角色創建的提示和驗證。
- EditableImage — 處理運行時創建和操作紋理。
- EditableMesh — 處理運行時操作網格幾何。
- WrapDeformer — 處理運行時操作隱形外圍幾何,以允許角色角色裝備 3D 服裝。
導入基礎身體
基礎身體作為用戶可以自訂和編輯的初始基礎。您可以使用自己的 Model,或通過 導入器 導入自訂資產並通過 角色設置 進行設置。
基礎身體必須遵循 Roblox 的角色規範,並必須包含如 15 個 MeshPart 實例,這些實例構成 6 個身體部件:頭部、軀幹、左臂、左腿、右臂、右腿,以及其他 角色組件。
有關正確配置的角色身體的參考和示例,請參見 角色參考。
實施編輯 API
要開發一個系統,讓用戶能夠在您的體驗中編輯角色的 MeshPart 實例以進行創建,請使用 EditableImage 進行紋理編輯,使用 EditableMesh 進行網格編輯,並使用 WrapDeformer 在網格編輯期間保持皮膚與 FACS 數據。
導入基礎身體後,使用以下腳本來設置您的 EditableImages、EditableMeshes 和 WrapDeformers。
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- 為 MeshPart 創建並附加 WrapDeformerlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- 為包圍目標的籠網格創建可編輯網格local cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- 將籠網格分配給 WrapDeformerwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- 從原始 MeshPart 創建可編輯網格local editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- 從可編輯網格生成新的 MeshPartlocal newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- 從原始 MeshPart 複製大小、位置和紋理newMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- 將新的 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)-- 重新使用 EditableImage 實例的紋理 ID 地圖local textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- 根據 WrapTarget 的存在配置 MeshPart-- 如果存在 WrapTarget,則添加一個帶有 EditableMesh 的 WrapDeformer 子項-- 否則,直接將 EditableMesh 應用於 MeshPartlocal wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- 配置 MeshPart 的 EditableImagesetupMeshTexture(descendant, textureIdToEditableImageMap)endend創建 EditableImage 工具,允許玩家對您的基礎身體進行重新上色、繪畫或添加貼紙。您可以利用 DrawImage()、DrawRectangle()、WritePixelsBuffer() 等 API。
對於進階變換,DrawImageTransformed() 允許您在將一個 EditableImage 繪製到另一個時指定位置、旋轉和縮放。類似地,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 = (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
end
AvatarCreationService:PromptCreateAvatarAsync() 接受一個 HumanoidDescription 參數,以表示意圖購買或創建的角色。對於角色創建,角色的 HumanoidDescription 必須包括為每 6 個身體部件創建的新資產 (Head, Torso, RightLeg, LeftLeg, RightArm, LeftArm)。可選地,它也可以包括一個新的 Hair 配件。
為了支持這個,HumanoidDescription 應包括 6 個 BodyPartDescription 子項。每個 BodyPartDescription.Instance 屬性都引用一個 Folder,其中包含構成身體部件的所有 MeshPart 實例。例如,LeftArm 資料夾包含 LeftHand、LeftUpperArm 和 LeftLowerArm 的 MeshParts。BodyPartDescription.BodyPart 屬性也應設置為相關的 Enum.BodyPart。
每 15 個 MeshPart 身體部件必須包括:
- 一個 EditableImage。
- 一個帶有 EditableMesh 的 WrapDeformer。
提供的 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