{itemType} 만들기
수익화
정책 및 가이드라인

경험 내 아바타 생성

*이 콘텐츠는 AI(베타)를 사용해 번역되었으며, 오류가 있을 수 있습니다. 이 페이지를 영어로 보려면 여기를 클릭하세요.

실시간으로 플레이어가 아바타 몸체를 생성, 사용자화 및 구매할 수 있는 경험을 공개할 수 있습니다. 구매가 완료되면, 이러한 커스터마이즈된 몸체는 플레이어의 Roblox 인벤토리에 직접 저장되어, 플레이어가 다른 경험에서 커스터마이즈된 아바타를 장착하고 착용할 수 있게 됩니다.

경험 소유자가 경험 내 아바타 생성을 구현하면, 아바타 아이템의 제작자경험 소유자로서 마켓플레이스 수수료에서 혜택을 받을 수 있습니다. 경험 내에서 생성된 자산이 조사될 경우, 해당 항목은 원래 생성된 경험에 대한 링크를 제공합니다.

Roblox의 아바타 제작기 데모에서 경험 내 생성을 테스트할 수 있습니다.

경험 내 생성을 구현하는 방법

다음 지침 및 코드 참조를 사용하여 처음으로 경험 내 아바타 생성 프로젝트를 작성하십시오. 아래 지침은 플레이어가 발행하기 전에 수정 및 사용자화할 수 있는 기본 몸체 Model을 사용합니다.

시작하기 전에, 다음을 숙지하십시오:

  • 아바타 모델 — 다음 구현은 Roblox의 15개 부품 사양을 충족하는 기본 몸체를 가져와야 합니다. 이 Model은 추가 사용자 선택 및 수정의 기본 역할을 합니다.
    • 기본 몸체는 Roblox의 아바타 몸체 지침을 충족해야 하며, 얼굴 리깅에 필요한 최소 FACS 제어 수를 포함해야 합니다.
  • 아바타 생성 토큰 — 아바타 생성을 구현하는 경험은 최소한 하나의 생성 토큰이 필요합니다. 이 토큰은 구매에 Robux가 필요하며, 경험 내 구매에 대한 가격 및 기타 판매 설정을 지정할 수 있습니다.
  • API 클래스
    • AvatarCreationService — 아바타 생성 프롬프트 및 검증을 처리합니다.
    • EditableImage — 텍스처의 런타임 생성 및 조작을 처리합니다.
    • EditableMesh — 메쉬 기하학의 런타임 조작을 처리합니다.
    • WrapDeformer — 아바타 캐릭터가 3D 의류를 장착할 수 있도록 허용하는 투명한 외부 케이지 기하학의 런타임 조작을 처리합니다.

기본 몸체 가져오기

기본 몸체는 사용자가 사용자화하고 편집할 수 있는 초기 기반으로 작용합니다. 자신만의 Model을 사용할 수도 있고, 수입기를 사용하여 사용자 정의 자산을 가져오고 아바타 설정을 통해 설정할 수 있습니다.

기본 몸체는 Roblox의 아바타 사양을 준수해야 하며, 6개 신체 부위: 머리, 몸통, 왼팔, 왼쪽 다리, 오른팔 및 오른쪽 다리로 구성된 15개의 MeshPart 인스턴스와 다른 아바타 구성 요소를 포함해야 합니다.

적절하게 구성된 아바타 몸체의 참조 및 샘플은 아바타 참조를 참조하십시오.

편집 API 구현

유저가 경험 내 아바타의 MeshPart 인스턴스를 편집할 수 있는 시스템을 개발하려면, EditableImage를 텍스처 편집에 사용하고, EditableMesh를 메쉬 편집에 사용하며, WrapDeformer를 메쉬 수정 중 피부화 및 FACS 데이터를 유지하는 데 사용하십시오.

  1. 기본 몸체를 가져온 후, 다음 스크립트를 사용하여 EditableImages, EditableMeshes, 및 WrapDeformers를 설정합니다.


    local AssetService = game:GetService("AssetService")
    local function setupBodyPart(meshPart, wrapTarget)
    -- MeshPart에 WrapDeformer를 생성하고 부착합니다.
    local wrapDeformer = Instance.new("WrapDeformer")
    wrapDeformer.Parent = meshPart
    -- WrapTarget의 케이지 메시를 위한 편집 가능한 메시 생성
    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,
    })
    -- 편집 가능한 메쉬에서 새로운 MeshPart 생성
    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)
    -- 이 TextureID에 대해 EditableImage가 이미 존재하면 새로 생성하지 않고 재사용
    if textureIdToEditableImageMap[meshPart.TextureID] then
    meshPart.TextureContent =
    Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])
    return
    end
    -- 새로운 EditableImage를 생성하고 텍스처 콘텐츠로 적용
    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 유무에 따라 MeshPart 설정
    -- WrapTarget이 존재하면 EditableMesh가 있는 WrapDeformer 하위 항목 추가
    -- 그렇지 않으면 MeshPart에 EditableMesh를 직접 적용
    local wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")
    if wrapTarget then
    setupBodyPart(descendant, wrapTarget)
    else
    setupRigidMesh(descendant)
    end
    -- MeshPart에 대해 EditableImage 설정
    setupMeshTexture(descendant, textureIdToEditableImageMap)
    end
    end
  2. 플레이어가 기본 몸체에 색상을 변경하고, 그리거나 스티커를 추가할 수 있는 EditableImage 도구를 만듭니다. DrawImage(), DrawRectangle(), WritePixelsBuffer()와 같은 API를 활용할 수 있습니다.

    • 고급 변환을 위해, DrawImageTransformed()를 사용하면 이미지를 다른 위치, 회전 및 비율로 그릴 수 있습니다. 비슷하게, DrawImageProjected()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 = (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. EditableMeshWrapDeformer가 반응하는 케이지 메시를 수정할 수 있습니다.

      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 --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("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 속성은 해당 신체 부위를 구성하는 모든 MeshPart 인스턴스를 포함하는 Folder를 참조합니다. 예를 들어, LeftArm 폴더는 LeftHand, LeftUpperArm, 및 LeftLowerArm MeshParts를 포함합니다. BodyPartDescription.BodyPart 속성도 해당 Enum.BodyPart로 설정해야 합니다.

15개의 MeshPart 신체 부위 각각은 다음을 포함해야 합니다:

제공된 HumanoidDescription은 의도된 생성에서 신체 부위 또는 액세서리를 나타내기 위해 미리 존재하는 자산 ID를 포함해서는 안 됩니다. 반면에, HumanoidDescriptionBodyTypeScale, HeadScale, HeightScale, WidthScale, 및 ProportionScale의 인간형 비율을 포함할 수 있습니다. 기본 몸체가 가져올 당시의 비율을 확인하여, 이를 HumanoidDescription에서 제공한 비율과 일치시켜야 합니다.

액세서리 포함하기

머리카락과 같은 액세서리를 포함하는 경우, HumanoidDescription 은 다음을 참조하는 AccessoryDescription 자식을 포함해야 합니다:

아바타 생성 토큰 생성

AvatarCreationService:PromptCreateAvatarAsync()아바타 생성 토큰 ID 매개변수를 사용합니다. 이 토큰은 귀하의 우주에서 생성의 키이며, 경험 내에서 아바타 생성을 위한 가격을 설정하는 데 사용할 수 있습니다. 토큰 생성에 대한 지침 및 추가 세부 정보는 아바타 생성 토큰을 참조하십시오.

토큰을 구매하고 생성한 후, Creator Hub에서 토큰을 점검하여 해당 ID를 찾고 이를 AvatarCreationService:PromptCreateAvatarAsync() API에 사용할 수 있습니다.

귀하의 경험에 따라 플레이어가 참여할 때 응답하기

경험 내에서 생성된 아바타 번들은 아바타가 생성된 원래 경험에 대한 귀속 링크를 포함합니다. 다른 플레이어가 아바타를 조사할 경우, 아바타가 생성된 경험을 방문할 수 있는 옵션을 제공하는 프롬프트가 표시됩니다.

이 귀속 링크를 사용하여 경험에 참여하는 플레이어를 처리하려면, Player:GetJoinData()를 사용하고 GameJoinContext에 대한 반환된 테이블을 파싱하십시오.

GameJoinContext는 다음 테이블 값을 포함합니다:

©2026 Roblox Corporation. Roblox 및 Roblox 로고, 'Powering Imagination'은 미국 및 기타 국가 내 당사의 등록 및 미등록 상표입니다.