您可以发布允许玩家在实时创创建 or 创作、自定义和购买虚拟形象身体的体验。购买后,这些自定义身体将直接保存到玩家的 Roblox 道具中,允许玩家在其他体验中装备和穿戴自定义虚拟形象。
在体验中实现虚拟形象创建的体验所有者可以获得市场佣金,既是虚拟形象项目的创建者,也是体验所有者。如果在体验中创建的资产被检查,该项目提供了链接到其创建的原始体验。
你可以在 Roblox 的 虚拟形象创作者 演示中测试经验创建。
如何实现经验内创作品
使用以下说明和代码参考创建你的第一个体验虚拟形象创作项目。以下说明使用一个基础身体 Model ,玩家可以在发布前修改和定制。
在开始之前,熟悉以关注中/正在关注内容:
- 虚拟形象创建代币 — 实现虚拟形象创建需要至少一个创建代币。这些代币需要 Robux 购买,允许您设置购买体验中的价格和其他销售设置。
- API 类别
- AvatarCreationService — 处理虚拟形象创建提示和验证。
- EditableImage — 处理图像的运行时创建和操纵。
- EditableMesh — 处理网格几何图形的运行时操作。
- WrapDeformer — 允许虚拟形象角色装备3D服装的隐形外部笼子几何操作运行时。
导入基础身体
基础身体作为用户可以自定义和编辑的初始基础。您可以使用自己的 Model , 或导入自定义资产以使用 3D 导入器 并通过 虚拟形象设置 设置。
基础体必须遵守 Roblox 的虚拟形象规格,并必须包含组件,例如 15 MeshPart 例组成 6 个身体部位:头部、躯干、左臂、左腿、右臂和右腿,以及其他 虚拟形象组件。
有关正确配置虚拟形象身体的参考和示例,请参阅虚拟形象参考。
实现编辑 API
要开发一个系统,让用户在体验中编辑虚拟形象上的 MeshPart 实例以进行创作品,使用 EditableImage 对纹理进行编辑, EditableMesh 对网格进行编辑,并 WrapDeformer 保留皮肤和 FACS 数据在网格编辑期间。
导入基础身体后,使用以下脚本设置您的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: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))-- 从原始 MeshPart 复制大小、位置和纹理newMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- 将新的 MeshPart 返回到原始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重用可编辑图像实例的地图local textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- 根据 WrapTarget 存在配置网格部件-- 如果 WrapTarget 存在,添加一个带有可编辑网格的 WrapDeformer 子集-- 否则,直接将可编辑网格应用到网格零件local wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- 配置网格零件的可编辑图像setupMeshTexture(descendant, textureIdToEditableImageMap)endend创建 EditableImage 工具,允许玩家为你的基础身体重新上色、绘画或添加贴纸。您可以利用 API 在像 DrawImage() , DrawRectangle() , WritePixelsBuffer() 中。
对于高级变形,DrawImageTransformed()允许您在绘制一个可编辑图像到另一个时指定位置、旋转和缩放。同样, 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 响应的笼子网格。
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 必须包含为每个身体部位创建的新资产 ( 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 身体部分必须包括:
- An EditableImage .
- 一个 WrapDeformer 具有 EditableMesh 。
提供的 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 参数。这个代币是你宇宙中创造的钥匙,也是你可以使用它来从体验设置虚拟形象创建价格的东西。有关生成代币的说明和附加细节,请参阅虚拟形象创建代币。
购买和生成代币后,您可以在创作者中心检查代币以找到ID,然后使用它来执行 AvatarCreationService:PromptCreateAvatarAsync() API。

通过归属来回应玩家加入
体验中创建的虚拟形象包括一个链接到原始体验的虚拟形象创建的链接。如果虚拟形象被另一名玩家检查,将显示提示,提供访问虚拟形象创建的体验的选择。

要处理玩家使用此属性关联接加入您的体验,请使用 Player:GetJoinData() 并解析返回的表格 GameJoinContext 。
GameJoinContext 包括以下表值:
- ItemType — 可选 Enum.AvatarItemType
- AssetId — 可选 string
- OutfitId — 可选 string
- AssetType — 可选 Enum.AssetType