플레이어가 실시간으로 아바타 바디를 생성, 사용자 정의 및 구매할 수 있는 경험을 게시할 수 있습니다.구매하면 이러한 사용자 지정 바디는 플레이어의 Roblox 인벤토리에 직접 저장되어 플레이어가 다른 경험에서 사용자 지정 아바타를 장착하고 착용할 수 있습니다.
경험 내 아바타 생성을 구현하는 경험 소유자는 마켓플레이스 수수료(크리에이터 및 경험 소유자)를 두 가지 측면에서 활용합니다.경험 내에서 생성된 자산이 검사되면 항목은 원래 경험에 대한 링크를 제공합니다.
Roblox의 아바타 크리에이터 데모에서 경험 내 생성을 테스트할 수 있습니다.
경험 내 생성 작품방법
다음 지침과 코드 참조를 사용하여 첫 번째 경험 내 아바타 생성 프로젝트를 만듭니다.다음 지침에서는 게시하기 전에 플레이어가 수정하고 사용자 정의할 수 있는 기본 바디 Model를 사용합니다.
시작하기 전에 다음에 익숙해지세요:
- 아바타 모델 — 다음 구현에는 Roblox의 15부 사양에 맞는 기본 바디를 가져오는 것이 필요합니다.이 Model 는 추가 사용자 사용자 정의 및 수정을 위한 기반으로 사용됩니다.
- 기본 바디는 Roblox의 아바타 바디 가이드라인을 충족해야 하며, 얼굴 리그에 대한 FACS 컨트롤의 최소 수를 포함합니다.
- 아바타 생성 토큰 — 아바타 생성 경험에는 최소 하나의 생성 토큰이 필요합니다.이 토큰은 Robux를 구매하고 경험 내에서 이루어진 구매에 대한 가격 및 기타 판매 설정을 설정할 수 있도록 해야 합니다.
- API 클래스
- AvatarCreationService — 아바타 생성 요청 및 유효성 검사를 처리합니다.
- EditableImage — 텍스처의 런타임 생성 및 조작을 처리합니다.
- EditableMesh — 메쉬 기하구조의 런타임 조작을 처리합니다.
- WrapDeformer — 아바타 캐릭터가 3D 의류를 장착할 수 있도록 하는 투명한 외부 철갑 기하학의 런타임 조작을 처리합니다.
기본 신체가져오기
기본 바디는 사용자가 사용자 지정하고 편집할 수 있는 초기 기반으로 작동합니다.자신의 Model 를 사용하거나 3D 가져오기 와 함께 사용자 지정 자산을 가져와 아바타 설정을 통해 설정할 수 있습니다.
기본 바디는 Roblox의 아바타 사양을 준수해야 하며, 6개의 신체 부위(머리, 몸통, 왼쪽 팔, 왼쪽 다리, 오른쪽 팔, 오른쪽 다리)와 다른 아바타 구성 요소(5개)를 포함해야 합니다.
올바르게 구성된 아바타 바디의 참조 및 샘플은 아바타 참조에 참조하십시오.
편집 API 구현
경험에서 사용자가 작품위해 아바타의 MeshPart 인스턴스를 편집할 수 있는 시스템을 개발하려면 텍스처 편집을 위해 EditableImage 를 사용하고, 메쉬 편집에서 메쉬를 편집하기 위해 EditableMesh 을 사용하고, 피부 및 FACS 데이터를 유지하기 위해 WrapDeformer 을 사용합니다.
기본 신체가져온 후에는 다음 스크립트를 사용하여 EditableImages , EditableMeshes , 및 WrapDeformers 을 설정합니다.
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- 메시 파트에 WrapDeformer 생성 및 연결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))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로 EditableImage 인스턴스를 재사용하는 맵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플레이어가 기본 신체색상을 바꾸고, 그리기 또는 스티커를 추가할 수 있는 EditableImageAPI를 활용할 수 있습니다 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 는 기본 스킨 및 FACS 데이터를 유지하면서 렌더링된 MeshPart 기하형의 라이브 변형을 처리합니다.
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 는 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 신체 부위 각각에는 포함되어야 합니다:
- An EditableImage .
- A WrapDeformer 와 함께 EditableMesh.
제공된 HumanoidDescription는 의도된 작품신체 부위나 액세서리를 나타내기 위한 기존 자산 ID를 포함해서는 안됩니다.반면에, HumanoidDescription 는 인간형 저울의 BodyTypeScale , HeadScale , HeightScale , WidthScale , 및 ProportionScale 을 포함할 수 있습니다.기본 바디가 가져오는 비율을 신중하게 고려하여 제공된 비율과 일치하도록 합니다.Be mindful of the scales that a base body is imported with so that they match the scales provided to the HumanoidDescription .

액세서리 포함
헤어와 같은 장신구포함하는 경우, HumanoidDescription 는 다음과 같은 자식 AccessoryDescription 을 포함해야 합니다.
- AccessoryDescription.Instance 속성은 Accessory 인스턴스를 참조합니다.
- AccessoryDescription.AccessoryType 속성이 관련 Enum.AccessoryType로 설정됩니다.
- 창작물에 Enum.AccessoryType.Hair를 포함하는 경우, MeshPart는 EditableImage을 포함해야 합니다.그러나 자식 을 포함해서는 안되지만 직접 에 세트를 포함해야 합니다.
아바타 생성 토큰 생성Generate an avatar creation token
AvatarCreationService:PromptCreateAvatarAsync()는 아바타 생성 토큰 ID 매개변수를 사용합니다.이 토큰은 우주에서 창조물의 열쇠이며, 경험에서 아바타 생성 가격을 설정하는 데 사용할 수 있는 것입니다.토큰 생성에 대한 지침과 추가 세부 정보는 아바타 생성 토큰을 참조하십시오.
토큰을 구매하고 생성한 후 크리에이터 허브에서 토큰을 검사하여 AvatarCreationService:PromptCreateAvatarAsync()에 사용할 수 있는 ID를 찾을 수 있습니다.

공헌도에 따라 플레이어가 참여하도록 응답
경험 내에서 생성된 아바타 패키지에는 아바타가 생성된 원래 경험에 대한 공헌 링크가 포함됩니다.아바타가 다른 플레이어에 의해 검사되면 아바타가 생성된 경험을 방문할 수 있는 옵션을 제공하는 메시지가 표시됩니다.

이 공헌도 연결사용하여 경험에 참여하는 플레이어를 처리하려면 Player:GetJoinData()를 사용하고 반환된 테이블을 분석하십시오 GameJoinContext.
GameJoinContext 은 다음 테이블 값을 포함합니다:
- JoinSource — Enum.JoinSource
- 이 공헌도 링크에서 경험에 연결하면 Player 생성된 아이템입력을 나타내기 위해 Enum.JoinSource.CreatedItemAttribution 가 있습니다.
- ItemType — 옵션 Enum.AvatarItemType
- AssetId — 옵션 string
- OutfitId — 옵션 string
- AssetType — 옵션 Enum.AssetType