Vous pouvez publier une expérience qui permet aux joueurs de créer, personnaliser et acheter des corps d'avatar en temps réel. Lorsqu'ils sont achetés, ces corps personnalisés sont directement enregistrés dans l'inventaire Roblox du joueur, permettant aux joueurs d'équiper et de porter les avatars personnalisés dans d'autres expériences.
Les propriétaires d'expériences qui mettent en œuvre la création d'avatar en expérience bénéficient des commissions de marché en tant que créateur de l'objet d'avatar et propriétaire d'expérience. Si un actif créé en expérience est inspecté, l'objet fournit un lien vers l'expérience d'origine dans laquelle il a été créé.
Vous pouvez tester la création en expérience dans la démo Avatar Creator de Roblox.
Comment mettre en œuvre la création en expérience
Utilisez les instructions et références de code suivantes pour créer votre premier projet de création d'avatar en expérience. Les instructions suivantes utilisent un corps de base Model que les joueurs peuvent modifier et personnaliser avant de publier.
Avant de commencer, familiarisez-vous avec les éléments suivants :
- Modèles d'avatar — La mise en œuvre suivante nécessite l'importation d'un corps de base qui respecte les spécifications de 15 parties de Roblox. Ce Model sert de base pour une personnalisation et une modification supplémentaires par les utilisateurs.
- Le corps de base doit respecter les lignes directrices pour les corps d'avatar de Roblox, y compris le nombre minimum de contrôles FACS pour le rigging facial.
- Jetons de création d'avatar — Les expériences mettant en œuvre la création d'avatar nécessitent au moins un jeton de création. Ces jetons nécessitent des Robux pour les acheter et vous permettent de définir les prix et d'autres paramètres de vente pour les achats effectués en expérience.
- Classes API
- AvatarCreationService — Gère la demande de création d'avatar et la validation.
- EditableImage — Gère la création et la manipulation des textures à l'exécution.
- EditableMesh — Gère la manipulation de la géométrie des maillages à l'exécution.
- WrapDeformer — Gère la manipulation en temps réel de la géométrie de cage extérieure invisible qui permet aux personnages d'avatar d'équiper des vêtements 3D.
Importer un corps de base
Le corps de base agit comme la fondation initiale que les utilisateurs peuvent personnaliser et modifier. Vous pouvez utiliser votre propre Model, ou importer un actif personnalisé avec l'Importateur et configurer via Avatar Setup.
Les corps de base doivent respecter les spécifications d'avatar de Roblox et doivent inclure des composants comme les 15 instances MeshPart qui composent 6 parties du corps : tête, torse, bras gauche, jambe gauche, bras droit et jambe droite, ainsi que d'autres composants d'avatar.
Pour des références et des échantillons de corps d'avatar correctement configurés, consultez Les références d'avatar.
Implementer les API d'édition
Pour développer un système où les utilisateurs peuvent éditer les instances MeshPart sur un avatar dans votre expérience pour la création, utilisez EditableImage pour l'édition des textures, EditableMesh pour l'édition des maillages, et WrapDeformer pour maintenir les données de peau et FACS pendant les modifications de maillage.
Après avoir importé votre corps de base, utilisez le script suivant pour configurer vos EditableImages, EditableMeshes, et WrapDeformers.
local AssetService = game:GetService("AssetService")local function setupBodyPart(meshPart, wrapTarget)-- Créer et attacher un WrapDeformer au MeshPartlocal wrapDeformer = Instance.new("WrapDeformer")wrapDeformer.Parent = meshPart-- Créer un maillage modifiable pour le maillage de cage de la cible de wraplocal cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- Assigner le maillage de cage au WrapDeformerwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- Créer un maillage modifiable à partir du MeshPart d'originelocal editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- Générer un nouveau MeshPart à partir du maillage modifiablelocal newMeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(editableMesh))-- Copier la taille, la position et la texture du MeshPart d'originenewMeshPart.Size = meshPart.SizenewMeshPart.CFrame = meshPart.CFramenewMeshPart.TextureContent = meshPart.TextureContent-- Appliquer le nouveau MeshPart à l'originalmeshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- Si EditableImage existe déjà pour ce TextureID, réutilisez-le plutôt que d'en créer un nouveauif textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- Créer un nouvel EditableImage et l'appliquer comme contenu de texturelocal editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(meshPart.TextureID))textureIdToEditableImageMap[meshPart.TextureID] = editableImagemeshPart.TextureContent = Content.fromObject(editableImage)endlocal function setupModel(model)-- Mappage pour réutiliser les instances d'EditableImage par ID de texturelocal textureIdToEditableImageMap = {}for _, descendant in model:GetDescendants() doif not descendant:IsA("MeshPart") thencontinueend-- Configurer le MeshPart en fonction de la présence de WrapTarget-- Si WrapTarget est présent, ajouter un enfant WrapDeformer avec un EditableMesh-- Sinon, appliquer EditableMesh directement au MeshPartlocal wrapTarget = descendant:FindFirstChildOfClass("WrapTarget")if wrapTarget thensetupBodyPart(descendant, wrapTarget)elsesetupRigidMesh(descendant)end-- Configurer l'EditableImage pour le MeshPartsetupMeshTexture(descendant, textureIdToEditableImageMap)endendCréez des outils EditableImage qui permettent aux joueurs de recolorer, dessiner ou ajouter des autocollants à votre corps de base. Vous pouvez tirer parti des API comme DrawImage(), DrawRectangle(), WritePixelsBuffer().
Pour des transformations avancées, DrawImageTransformed() vous permet de spécifier la position, la rotation et l'échelle lors du dessin d'un EditableImage sur un autre. De même, DrawImageProjected() fonctionne de manière similaire à DrawImage() mais projette correctement l'image dessinée si l'instance EditableImage est utilisée avec 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
À l'aide de WrapDeformer et EditableMesh, créez des outils pour modifier les déformations de maillage sur votre corps.
WrapDeformer gère les déformations en direct de la géométrie MeshPart rendue tout en maintenant les données de peau et FACS sous-jacentes.
EditableMesh vous permet de modifier le maillage de cage auquel WrapDeformer répond.
- Utilisez WrapDeformer:SetCageMeshContent() pour appliquer l'instance EditableMesh qui représente le maillage de cage pertinent au WrapDeformer.
- Utilisez EditableMesh, tel que SetPosition(), pour déformer les sommets et éditer la forme du MeshPart.
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
Créer un prompt de création
Après avoir configuré votre corps de base et les API d'édition, créez un prompt pour que les utilisateurs puissent créer et acheter depuis l'expérience en utilisant AvatarCreationService:PromptCreateAvatarAsync().
export type BodyPartInfo = {
bodyPart: Enum.BodyPart,
instance: Instance -- Dossier contenant des MeshParts créés
}
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("Téléchargement réussi avec BundleId : ", bundleIdOrErrorMessage)
print("Téléchargement réussi avec OutfitId : ", outfitId)
else
print("Téléchargement échoué avec le message d'erreur :", bundleIdOrErrorMessage)
end
else
print("Création d'avatar échouée")
end
end
AvatarCreationService:PromptCreateAvatarAsync() prend un paramètre HumanoidDescription pour représenter l'avatar destiné à l'achat ou à la création. Pour la création d'avatar, la HumanoidDescription du personnage doit inclure de nouveaux actifs à créer pour chacune des 6 parties du corps (Head, Torso, RightLeg, LeftLeg, RightArm, LeftArm). En option, elle peut également inclure un nouvel accessoire de type Hair.
Pour soutenir cela, le HumanoidDescription doit inclure 6 enfants BodyPartDescription. Chaque propriété BodyPartDescription.Instance fait référence à un Folder qui contient toutes les instances de MeshPart qui composent la partie du corps. Par exemple, le dossier LeftArm contient LeftHand, LeftUpperArm, et LeftLowerArm MeshParts. La propriété BodyPartDescription.BodyPart doit également être définie sur l'Enum.BodyPart pertinent.
Chacune des 15 parties MeshPart doit inclure :
- Un EditableImage.
- Un WrapDeformer avec un EditableMesh.
Le HumanoidDescription fourni ne doit inclure aucun ID d'actif préexistant pour représenter les parties du corps ou les accessoires sur la création prévue. D'autre part, le HumanoidDescription peut inclure les échelles humanoïdes de BodyTypeScale, HeadScale, HeightScale, WidthScale, et ProportionScale. Veuillez faire attention aux échelles avec lesquelles un corps de base est importé afin qu'elles correspondent aux échelles fournies au HumanoidDescription.

Inclure des accessoires
Si vous incluez un accessoire, tel que des cheveux, la HumanoidDescription doit inclure un enfant AccessoryDescription où :
- La propriété AccessoryDescription.Instance fait référence à l'instance Accessory.
- La propriété AccessoryDescription.AccessoryType est définie sur l'Enum.AccessoryType pertinent.
- Dans le cas de l'inclusion de Enum.AccessoryType.Hair dans vos créations, le MeshPart doit inclure un EditableImage. Cependant, il ne doit pas inclure un enfant WrapDeformer mais doit inclure un EditableMesh défini directement sur le MeshPart.
Générer un jeton de création d'avatar
AvatarCreationService:PromptCreateAvatarAsync() prend un paramètre ID de jeton de création d'avatar. Ce jeton est la clé pour les créations de votre univers, et c'est ce que vous pouvez utiliser pour définir le prix de la création d'avatar depuis votre expérience. Pour des instructions et des détails supplémentaires sur la génération de jetons, consultez Jetons de création d'avatar.
Après avoir acheté et généré votre jeton, vous pouvez inspecter le jeton dans le Creator Hub pour trouver l'ID que vous pouvez ensuite utiliser pour l'API AvatarCreationService:PromptCreateAvatarAsync().

Répondre aux joueurs qui rejoignent par attribution
Les paquets d'avatar créés en expérience incluent un lien d'attribution vers l'expérience d'origine dans laquelle l'avatar a été créé. Si l'avatar est inspecté par un autre joueur, une invite s'affiche offrant la possibilité de visiter l'expérience où l'avatar a été créé.

Pour gérer les joueurs rejoignant votre expérience en utilisant ce lien d'attribution, utilisez Player:GetJoinData() et parsez la table retournée pour GameJoinContext.
GameJoinContext inclut les valeurs de table suivantes :
- JoinSource — Enum.JoinSource
- Un Player rejoignant votre expérience via ce lien d'attribution aura Enum.JoinSource.CreatedItemAttribution pour indiquer l'entrée depuis un élément créé.
- ItemType — optionnel Enum.AvatarItemType
- AssetId — optionnel string
- OutfitId — optionnel string
- AssetType — optionnel Enum.AssetType