体验中的虚拟形象作品

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

您可以发布允许玩家在实时创创建 or 创作、自定义和购买虚拟形象身体的体验。购买后,这些自定义身体将直接保存到玩家的 Roblox 道具中,允许玩家在其他体验中装备和穿戴自定义虚拟形象。

在体验中实现虚拟形象创建的体验所有者可以获得市场佣金,既是虚拟形象项目的创建者,也是体验所有者。如果在体验中创建的资产被检查,该项目提供了链接到其创建的原始体验。

你可以在 Roblox 的 虚拟形象创作者 演示中测试经验创建。

如何实现经验内创作品

使用以下说明和代码参考创建你的第一个体验虚拟形象创作项目。以下说明使用一个基础身体 Model ,玩家可以在发布前修改和定制。

在开始之前,熟悉以关注中/正在关注内容:

  • 虚拟形象模型 — 以下实现需要导入满足 Roblox 15 部分规格的基础身体。这 Model 作为额外用户自定义和修改的基础。
    • 基础身体必须满足 Roblox 的 虚拟形象身体指南,包括面部绑骨架化的最低数量的 FACS 控件。
  • 虚拟形象创建代币 — 实现虚拟形象创建需要至少一个创建代币。这些代币需要 Robux 购买,允许您设置购买体验中的价格和其他销售设置。
  • API 类别

导入基础身体

基础身体作为用户可以自定义和编辑的初始基础。您可以使用自己的 Model , 或导入自定义资产以使用 3D 导入器 并通过 虚拟形象设置 设置。

基础体必须遵守 Roblox 的虚拟形象规格,并必须包含组件,例如 15 MeshPart 例组成 6 个身体部位:头部、躯干、左臂、左腿、右臂和右腿,以及其他 虚拟形象组件

有关正确配置虚拟形象身体的参考和示例,请参阅虚拟形象参考

实现编辑 API

要开发一个系统,让用户在体验中编辑虚拟形象上的 MeshPart 实例以进行创作品,使用 EditableImage 对纹理进行编辑, EditableMesh 对网格进行编辑,并 WrapDeformer 保留皮肤和 FACS 数据在网格编辑期间。

  1. 导入基础身体后,使用以下脚本设置您的EditableImagesEditableMeshesWrapDeformers


    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))
    end
    local 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.Size
    newMeshPart.CFrame = meshPart.CFrame
    newMeshPart.TextureContent = meshPart.TextureContent
    -- 将新的 MeshPart 返回到原始
    meshPart:ApplyMesh(newMeshPart)
    end
    local function setupMeshTexture(meshPart, textureIdToEditableImageMap)
    -- 如果可编辑图像已存在于此纹理ID,请重复使用它而不是制作新的图像
    if textureIdToEditableImageMap[meshPart.TextureID] then
    meshPart.TextureContent =
    Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])
    return
    end
    -- 创建一个新的可编辑图像,并将其应用为纹理内容
    local editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(meshPart.TextureID))
    textureIdToEditableImageMap[meshPart.TextureID] = editableImage
    meshPart.TextureContent = Content.fromObject(editableImage)
    end
    local function setupModel(model)
    -- 按照纹理ID重用可编辑图像实例的地图
    local textureIdToEditableImageMap = {}
    for _, descendant in model:GetDescendants() do
    if not descendant:IsA("MeshPart") then
    continue
    end
    -- 根据 WrapTarget 存在配置网格部件
    -- 如果 WrapTarget 存在,添加一个带有可编辑网格的 WrapDeformer 子集
    -- 否则,直接将可编辑网格应用到网格零件
    local wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")
    if wrapTarget then
    setupBodyPart(descendant, wrapTarget)
    else
    setupRigidMesh(descendant)
    end
    -- 配置网格零件的可编辑图像
    setupMeshTexture(descendant, textureIdToEditableImageMap)
    end
    end
  2. 创建 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
      )
      end
      local 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)
      end
      local 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).Unit
      local 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
  3. 使用 WrapDeformerEditableMesh 创建工具,用于编辑你身体上的网格变形。

    1. WrapDeformer 处理渲染的 MeshPart 几何图形的实时变形,同时保留底层皮肤和 FACS 数据。

    2. EditableMesh 允许你修改 WrapDeformer 响应的笼子网格。

      1. 使用 WrapDeformer:SetCageMeshContent() 将代表相关笼子网格的实例 EditableMesh 应用到 WrapDeformer
      2. 使用 EditableMesh ,例如 SetPosition() ,扭曲边角并编辑 MeshPart 的形状。

      local function deformBodyPart(
      meshPart: MeshPart,
      controlPointCenter: Vector3,
      controlPointRadius: number,
      controlPointDeformation: Vector3
      )
      local wrapTarget = meshPart:FindFirstChildWhichIsA("WrapTarget")
      local cageMeshId = wrapTarget.CageMeshId
      local wrapDeformer = Instance.new("WrapDeformer")
      wrapDeformer.Parent = meshPart
      local cageEditableMesh = AssetService:CreateEditableMeshAsync(cageMeshId)
      local verticesWithinSphere =
      cageEditableMesh:FindVerticesWithinSphere(controlPointCenter, controlPointRadius)
      for _, vertexId in verticesWithinSphere do
      local vertexPosition = cageEditableMesh:GetPosition(vertexId)
      cageEditableMesh:SetPosition(vertexId, vertexPosition + controlPointDeformation)
      end
      wrapDeformer: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 MeshPartsBodyPartDescription.BodyPart 属性也应设置为相关的Enum.BodyPart

每个 15 MeshPart 身体部分必须包括:

提供的 HumanoidDescription 不应包含任何现有资产ID来代表意图创作品的身体部位或配件。另一方面,HumanoidDescription 可能包含 BodyTypeScaleHeadScaleHeightScaleWidthScaleProportionScale 的人形尺寸。注意导入基础体的尺寸,使其与提供给 HumanoidDescription 的尺寸匹配。

包括配件

如果包含饰品,例如头发,那么 HumanoidDescription 应包含一个子 AccessoryDescription ,其中:

生成一个虚拟形象创建代币

AvatarCreationService:PromptCreateAvatarAsync() 需要一个 虚拟形象创建代币 ID 参数。这个代币是你宇宙中创造的钥匙,也是你可以使用它来从体验设置虚拟形象创建价格的东西。有关生成代币的说明和附加细节,请参阅虚拟形象创建代币

购买和生成代币后,您可以在创作者中心检查代币以找到ID,然后使用它来执行 AvatarCreationService:PromptCreateAvatarAsync() API。

通过归属来回应玩家加入

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

要处理玩家使用此属性关联接加入您的体验,请使用 Player:GetJoinData() 并解析返回的表格 GameJoinContext

GameJoinContext 包括以下表值:

  • JoinSourceEnum.JoinSource
    • 从这个属性链接加入您的体验将有 指示从创建的物品目中进入。
  • ItemType — 可选 Enum.AvatarItemType
  • AssetId — 可选 string
  • OutfitId — 可选 string
  • AssetType — 可选 Enum.AssetType