Puedes publicar una experiencia que permita a los jugadores crear, personalizar y comprar cuerpos de avatar en tiempo real. Cuando se compran, estos cuerpos personalizados se guardan directamente en el inventario de Roblox del jugador, permitiendo a los jugadores equipar y usar los avatares personalizados en otras experiencias.
Los dueños de experiencias que implementan la creación de avatares en la experiencia se benefician de comisiones del Mercado como creadores del artículo de avatar y dueños de la experiencia. Si se inspecciona un activo creado en la experiencia, el artículo proporciona un enlace a la experiencia original en la que fue creado.
Puedes probar la creación dentro de la experiencia en la demostración de Avatar Creator.
Cómo implementar la creación en la experiencia
Utiliza las siguientes instrucciones y referencias de código para crear tu primer proyecto de creación de avatar en la experiencia. Las siguientes instrucciones utilizan un cuerpo base Model que los jugadores pueden modificar y personalizar antes de publicar.
Antes de comenzar, familiarízate con lo siguiente:
- Modelos de avatar: La siguiente implementación requiere importar un cuerpo base que cumpla con las especificaciones de 15 partes de Roblox. Este Model sirve como base para la personalización y modificación adicional del usuario.
- El cuerpo base debe cumplir con las directrices del cuerpo del avatar de Roblox, incluyendo el número mínimo de controles FACS para la rigidez facial.
- Tokens de creación de avatar: Las experiencias que implementan la creación de avatares requieren al menos un token de creación. Estos tokens requieren Robux para ser comprados y te permiten establecer precios y otras configuraciones de venta para compras realizadas en la experiencia.
- Clases de API
- AvatarCreationService: Maneja la invitación y validación para la creación de avatares.
- EditableImage: Maneja la creación y manipulación en tiempo de ejecución de texturas.
- EditableMesh: Maneja la manipulación en tiempo de ejecución de la geometría de la malla.
- WrapDeformer: Maneja la manipulación en tiempo de ejecución de la geometría de la jaula externa invisible que permite a los personajes de avatar equipar ropa 3D.
Importar un cuerpo base
El cuerpo base actúa como la base inicial que los usuarios pueden personalizar y editar. Puedes usar tu propio Model, o importar un activo personalizado con el Importador y configurarlo a través de Configuración del Avatar.
Los cuerpos base deben adherirse a las especificaciones de avatar de Roblox e incluir componentes como las 15 instancias de MeshPart que componen 6 partes del cuerpo: cabeza, torso, brazo izquierdo, pierna izquierda, brazo derecho y pierna derecha, así como otros componentes de avatar.
Para referencias y muestras de cuerpos de avatar configurados correctamente, consulta Referencias de Avatar.
Implementar APIs de edición
Para desarrollar un sistema donde los usuarios puedan editar las instancias de MeshPart en un avatar en tu experiencia para la creación, utiliza EditableImage para la edición de texturas, EditableMesh para la edición de mallas, y WrapDeformer para mantener los datos de skinning y FACS durante las ediciones de la malla.
Después de importar tu cuerpo base, utiliza el siguiente script para configurar tus EditableImages, EditableMeshes, y WrapDeformers.
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- Crea y conecta un WrapDeformer al MeshPartlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- Crea una malla editable para la malla de la caja del objetivo de envolturalocal cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- Asigna la malla de la jaula al WrapDeformerwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- Crea una malla editable a partir del MeshPart originallocal editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- Genera un nuevo MeshPart a partir de la malla editablelocal newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- Copia el tamaño, la posición y la textura del MeshPart originalnewMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- Aplica el nuevo MeshPart de vuelta al originalmeshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- Si EditableImage ya existe para este TextureID, reutilízalo en lugar de crear uno nuevoif textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- Crea un nuevo EditableImage y aplícalo como el contenido de texturalocal editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(meshPart.TextureID))textureIdToEditableImageMap[meshPart.TextureID] = editableImagemeshPart.TextureContent = Content.fromObject(editableImage)endlocal function setupModel(model)-- Mapa para reutilizar instancias de EditableImage por ID de texturalocal textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- Configura MeshPart según la presencia de WrapTarget-- Si WrapTarget está presente, agrega un hijo WrapDeformer con una EditableMesh-- De lo contrario, aplica EditableMesh directamente al MeshPartlocal wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- Configura el EditableImage para el MeshPartsetupMeshTexture(descendant, textureIdToEditableImageMap)endendCrea herramientas EditableImage que permitan a los jugadores recolorear, dibujar o agregar calcomanías a tu cuerpo base. Puedes aprovechar APIs como DrawImage(), DrawRectangle(), WritePixelsBuffer().
Para transformaciones avanzadas, DrawImageTransformed() te permite especificar la posición, rotación y escala al dibujar un EditableImage sobre otro. De manera similar, DrawImageProjected() funciona mucho como DrawImage(), pero proyecta la imagen dibujada correctamente si la instancia de EditableImage se utiliza con un 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 = (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
Usando WrapDeformer y EditableMesh, crea herramientas para editar deformaciones de malla en tu cuerpo.
WrapDeformer maneja las deformaciones en vivo de la geometría de MeshPart renderizada mientras mantiene los datos de skinning y FACS subyacentes.
EditableMesh te permite modificar la malla de jaula a la que responde el WrapDeformer.
- Usa WrapDeformer:SetCageMeshContent() para aplicar la instancia de EditableMesh que representa la malla de jaula relevante al 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
Crear un aviso de creación
Después de configurar tu cuerpo base y las APIs de edición, crea un aviso para que los usuarios creen y compren desde la experiencia utilizando AvatarCreationService:PromptCreateAvatarAsync().
export type BodyPartInfo = {
bodyPart: Enum.BodyPart,
instance: Instance -- Carpeta con Los MeshParts Creados
}
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("Subido con éxito con BundleId: ", bundleIdOrErrorMessage)
print("Subido con éxito con OutfitId: ", outfitId)
else
print("Subida no exitosa con mensaje de error:", bundleIdOrErrorMessage)
end
else
print("Creación de avatar no exitosa")
end
end
AvatarCreationService:PromptCreateAvatarAsync() toma un parámetro HumanoidDescription para representar el avatar destinado a la compra o creación. Para la creación de avatares, la HumanoidDescription del personaje debe incluir nuevos activos que se crearán para cada una de las 6 partes del cuerpo (Head, Torso, RightLeg, LeftLeg, RightArm, LeftArm). Opcionalmente, también puede incluir un accesorio nuevo de tipo Hair.
Para admitir esto, la HumanoidDescription debe incluir 6 hijos BodyPartDescription. Cada propiedad BodyPartDescription.Instance hace referencia a una Folder que incluye todas las instancias de MeshPart que componen la parte del cuerpo. Por ejemplo, la carpeta LeftArm contiene LeftHand, LeftUpperArm, y LeftLowerArm MeshParts. La propiedad BodyPartDescription.BodyPart también debe establecerse en el Enum.BodyPart relevante.
Cada una de las 15 partes del cuerpo de MeshPart debe incluir:
- Un EditableImage.
- Un WrapDeformer con un EditableMesh.
La HumanoidDescription proporcionada no debe incluir ningún ID de activo preexistente para representar partes del cuerpo o accesorios en la creación prevista. Por otro lado, la HumanoidDescription puede incluir las escalas humanoides de BodyTypeScale, HeadScale, HeightScale, WidthScale y ProportionScale. Ten en cuenta las escalas con las que se importa un cuerpo base para que coincidan con las escalas proporcionadas a la HumanoidDescription.

Incluir accesorios
Si se incluye un accesorio, como cabello, la HumanoidDescription debe incluir un niño AccessoryDescription donde:
- La propiedad AccessoryDescription.Instance hace referencia a la instancia de Accessory.
- La propiedad AccessoryDescription.AccessoryType se establece en el Enum.AccessoryType relevante.
- En el caso de incluir Enum.AccessoryType.Hair en tus creaciones, el MeshPart debe incluir un EditableImage. Sin embargo, no debe incluir un hijo WrapDeformer, sino que debe incluir un EditableMesh establecido directamente en el MeshPart.
Generar un token de creación de avatar
AvatarCreationService:PromptCreateAvatarAsync() toma un parámetro de ID de Token de Creación de Avatar. Este token es la clave para las creaciones de tu universo, y es lo que puedes usar para establecer el precio de la creación del avatar desde tu experiencia. Para obtener instrucciones y detalles adicionales sobre la generación de tokens, consulta Tokens de Creación de Avatar.
Después de comprar y generar tu token, puedes inspeccionar el token en el Creator Hub para encontrar el ID que puedes usar para la API AvatarCreationService:PromptCreateAvatarAsync().

Responder a los jugadores que se unen por atribución
Los paquetes de avatar creados en la experiencia incluyen un enlace de atribución a la experiencia original en la que se creó el avatar. Si otro jugador inspecciona el avatar, se muestra un aviso que brinda una opción para visitar la experiencia donde se creó el avatar.

Para manejar a los jugadores que se unen a tu experiencia usando este enlace de atribución, utiliza Player:GetJoinData() y analiza la tabla devuelta para GameJoinContext.
GameJoinContext incluye los siguientes valores de tabla:
- JoinSource — Enum.JoinSource
- Un Player que se une a tu experiencia a través de este enlace de atribución tendrá Enum.JoinSource.CreatedItemAttribution para indicar entrada desde un artículo creado.
- ItemType — opcional Enum.AvatarItemType
- AssetId — opcional string
- OutfitId — opcional string
- AssetType — opcional Enum.AssetType