経験中のアバターの作作品

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

プレイヤーがリアルタイムでアバターボディを作成、カスタマイズ、購入できる体験を公開できます。購入すると、これらのカスタムボディはプレイヤーの Roblox インベントリに直接持ち物リストされ、プレイヤーが他のエクスペリエンスでカスタムアバターを装備して着用できるようにします。

経験中のアバター作成を実装する経験所有者は、マーケットプレイスの手数料 を両方、 アバターアイテムのクリエーター経験所有者 の両方から受け取ります。エクスペリエンス内で作成されたアセットが調査されると、アイテムは作成されたオリジナルのエクスペリエンスへのリンクを提供します。

RobloRoblox(ロブロックス) の アバタークリエーター デモで、体験中の作成をテストできます。

経験中の作作品を実装する方法

次の指示とコード参照を使用して、最初のバーチャル空間アバター作成プロジェクトを作成します。次の指示では、プレイヤーが公開する前に変更してカスタマイズできるベースボディ Model を使用します。

開始する前に、フォロー中のことを理解してください:

  • アバターモデル — 次の実装では、Roblox の 15部分仕様に対応するベースボディをインポートする必要があります。この Model は、追加のユーザカスタマイズと修正の基礎として機能します。
  • アバター作成トークン — アバター作成を実装するエクスペリエンスには、少なくとも 1つの作成トークンが必要です。これらのトークンは、Robux を購入して、体験内で行われた購入の価格とその他の販売設定を設定できるようにする必要があります。
  • APIクラス
    • AvatarCreationService — アバター作成の促進と検証を処理します。
    • EditableImage — テクスチャの作成と操作を処理します。
    • EditableMesh — メッシュジオメトリのランタイム操作を処理します。
    • WrapDeformer — アバターキャラクターが3D衣装を装備できるようにする透明な外部ケージの幾何操作のランタイム処理。

基本ボディをインポートする

ベースボディは、ユーザーがカスタマイズして編集できる最初の基礎として機能します。自分の Model を使用したり、3Dインポーター でカスタムアセットをインポートして、アバター設定 を通じて設定できます。

ベースボディは、Roblox のアバター仕様に従わなければならず、6つのボディパーツ(頭、胴体、左腕、左脚、右腕、右脚)と他の アバターコンポーネント を構成する15のインスタンスなどのコンポーネントを含まなければなりません。

正しく構成されたアバターボディの参照とサンプルについては、アバター参照 を参照してください。

編集 API を実装する

エクスペリエンス内のアバターでユーザーが編集できる MeshPart インスタンスを開作品し、テクスチャ編集のために EditableImage 、メッシュ編集のために EditableMesh 、メッシュ編集中にスキンと FACS データを維持するために WrapDeformer を使用します。

  1. ベースボディをインポートした後、次のスクリプトを使用して EditableImagesEditableMeshes、および 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))
    end
    local function setupRigidMesh(meshPart)
    -- オリジナルの MeshPart から編集可能なメッシュを作成する
    local editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {
    FixedSize = true,
    })
    -- 編集可能なメッシュから新しいメッシュパーツを生成
    local newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))
    -- オリジナルのメッシュパーツからサイズ、位置、テクスチャをコピー
    newMeshPart.Size = meshPart.Size
    newMeshPart.CFrame = meshPart.CFrame
    newMeshPart.TextureContent = meshPart.TextureContent
    -- 新しいメッシュパーツを元に戻す
    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で再使用可能な EditableImage インスタンスのマップ
    local textureIdToEditableImageMap = {}
    for _, descendant in model:GetDescendants() do
    if not descendant:IsA("MeshPart") then
    continue
    end
    -- WrapTarget の存在に基づいてメッシュパーツを構成する
    -- WrapTarget が存在する場合、編集可能なメッシュを持つ WrapDeformer の子を追加します
    -- そうでない場合は、MeshPart に直接 EditableMesh を適用します
    local wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")
    if wrapTarget then
    setupBodyPart(descendant, wrapTarget)
    else
    setupRigidMesh(descendant)
    end
    -- メッシュパーツの編集可能な画像を構成する
    setupMeshTexture(descendant, textureIdToEditableImageMap)
    end
    end
  2. プレイヤーがベースボディに色を塗り加えたり、ステッカーを描画したり、追加したりできる 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
      )
      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 には、6つの身体部分 ( Head , Torso , RightLeg , LeftLeg , RightArm , LeftArm ) のそれぞれに作成される新しいアセットが含まれている必要があります。オプションで、新しい Hair アクセサリーも含めることができます。

これをサポートするために、HumanoidDescription は 6 BodyPartDescription の子供を含める必要があります。それぞれの BodyPartDescription.Instance プロパティは、体の部分を構成するすべての Folder インスタンスを含む MeshPart を参照します。たとえば、 フォルダには , , そして が含まれています。BodyPartDescription.BodyPart プロパティも、関連する Enum.BodyPart に設定する必要があります。

15の各MeshPartボディパーツは次のものを含まなければなりません:

提供された HumanoidDescription は、意図した作作品に体部またはアクセサリを表現するための既存のアセットIDを含まないでしょう。一方、HumanoidDescription には、BodyTypeScaleHeadScaleHeightScaleWidthScale 、およびProportionScaleが含まれる可能性があります。ベースボディがインポートされるスケールを注意して、HumanoidDescription に提供されたスケールと一致するようにしてください。

アクセサリを含む

アクセサリーを含める場合、髪など、 には、次のような子を含める必要があります:

アバター作成トークンを生成する

AvatarCreationService:PromptCreateAvatarAsync()アバター作成トークンID パラメータを取ります。このトークンは、あなたの宇宙からの創造の鍵であり、あなたの経験からアバター作成の価格を設定するために使用できるものです。トークンの生成に関する指示と追加詳細は、アバター作成トークン を参照してください。

トークンを購入して生成した後、クリエイターハブでトークンを調べて、AvatarCreationService:PromptCreateAvatarAsync() API に使用できるIDを見つけることができます。

属性によって参加するプレイヤーに応答する

経験内で作成されたアバターバンドルには、アバターが作成されたオリジナルエクスペリエンスへの貢献リンクが含まれています。アバターが別のプレイヤーによって検査されると、アバターが作成されたエクスペリエンスを訪問するオプションが表示されるプロンプトが表示されます。

この属性リンクを使用して、エクスペリエンスに参加するプレイヤーを処理するには、Player:GetJoinData() を使用し、返されたテーブルを解析して GameJoinContext

GameJoinContext には次のテーブル値が含まれます: