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 sauvegardés directement dans l'inventaire Roblox du joueur, ce qui permet aux joueurs de les équiper et de porter les avatars personnalisés dans d'autres expériences.
Les propriétaires d'expérience 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'article d'avatar et propriétaire de l'expérience. Si un actif créé en expérience est inspecté, l'article fournit un lien vers l'expérience originale dans laquelle il a été créé.
Vous pouvez tester la création en expérience dans la démonstration d'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 ce qui suit :
- Modèles d'avatar — La mise en œuvre suivante nécessite d'importer un corps de base qui respecte les spécifications de 15 pièces de Roblox. Ce Model sert de base pour la personnalisation et la modification supplémentaires par l'utilisateur.
- Le corps de base doit répondre aux directives concernant le corps de l'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 être achetés et vous permettent de définir les prix et d'autres paramètres de vente pour les achats effectués en expérience.
- Classes d'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 de maillage à l'exécution.
- WrapDeformer — Gère la manipulation à l'exécution de la géométrie invisible de la cage extérieure qui permet aux personnages d'avatar d'équiper des vêtements 3D.
Importer un corps de base
Le corps de base sert de fondation initiale que les utilisateurs peuvent personnaliser et modifier. Vous pouvez utiliser votre propre Model, ou importer un actif personnalisé avec l'Importateur et le configurer via Avatar Setup.
Les corps de base doivent respecter les spécifications d'avatar de Roblox et doivent inclure des composants tels que les 15 instances de 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 exemples de corps d'avatar correctement configurés, consultez Références d'avatar.
Mettre en œuvre les API d'édition
Pour développer un système où les utilisateurs peuvent modifier les instances MeshPart sur un avatar dans votre expérience pour la création, utilisez EditableImage pour l'édition de texture, EditableMesh pour l'édition de maillage et WrapDeformer pour maintenir les données de skinning 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 éditable pour le maillage de la cage de la ciblelocal cageEditableMesh: EditableMesh =AssetService:CreateEditableMeshAsync(Content.fromUri(wrapTarget.CageMeshId), {FixedSize = true,})-- Assigner le maillage de la cage au WrapDeformerwrapDeformer:SetCageMeshContent(Content.fromObject(cageEditableMesh))endlocal function setupRigidMesh(meshPart)-- Créer un maillage éditable à partir du MeshPart d'originelocal editableMesh = AssetService:CreateEditableMeshAsync(Content.fromUri(meshPart.MeshId), {FixedSize = true,})-- Générer un nouveau MeshPart à partir du maillage éditablelocal 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 au d'originemeshPart:ApplyMesh(newMeshPart)endlocal function setupMeshTexture(meshPart, textureIdToEditableImageMap)-- Si EditableImage existe déjà pour cet TextureID, le réutiliser plutôt que d'en créer un nouveauif textureIdToEditableImageMap[meshPart.TextureID] thenmeshPart.TextureContent =Content.fromObject(textureIdToEditableImageMap[meshPart.TextureID])returnend-- Créer une nouvelle 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)-- Carte pour réutiliser des instances 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 une WrapDeformer enfant 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'une EditableImage sur une 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 éditer les déformations de maillage sur votre corps.
WrapDeformer gère les déformations en direct de la géométrie de MeshPart rendue tout en maintenant les données de skinning 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, comme SetPosition(), pour déformer les vertices et modifier 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 une invite de création
Après avoir configuré votre corps de base et les API d'édition, créez une invite pour les utilisateurs afin de créer et d'acheter depuis l'expérience en utilisant AvatarCreationService:PromptCreateAvatarAsync().
export type BodyPartInfo = {
bodyPart: Enum.BodyPart,
instance: Instance --Dossier avec les 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
endAvatarCreationService: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 supporter cela, la 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 le Enum.BodyPart pertinent.
Chacune des 15 parties du corps MeshPart doit inclure :
- Un EditableImage.
- Un WrapDeformer avec un EditableMesh.
La HumanoidDescription fournie ne doit pas inclure d'ID d'actifs préexistants pour représenter les parties du corps ou les accessoires sur la création prévue. En revanche, la HumanoidDescription peut inclure les échelles humanoïdes de BodyTypeScale, HeadScale, HeightScale, WidthScale, et ProportionScale. Faites attention aux échelles avec lesquelles un corps de base est importé afin qu'elles correspondent aux échelles fournies à la 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 le 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 une enfant WrapDeformer mais doit inclure un EditableMesh appliqué 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 il est ce que vous pouvez utiliser pour définir le prix de 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 le AvatarCreationService:PromptCreateAvatarAsync() API.

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 originale où l'avatar a été créé. Si l'avatar est inspecté par un autre joueur, une invite s'affiche offrant une option 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 analysez la table renvoyée pour GameJoinContext.
GameJoinContext inclut les valeurs de table suivantes :
- JoinSource — Enum.JoinSource
- Un Player rejoignant votre expérience à partir de ce lien d'attribution aura Enum.JoinSource.CreatedItemAttribution pour indiquer une entrée à partir d'un article créé.
- ItemType — Enum.AvatarItemType optionnel
- AssetId — string optionnel
- OutfitId — string optionnel
- AssetType — Enum.AssetType optionnel