> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/custom-components.md).

# Componentes personalizados

Crea un componente personalizado para manejar datos específicos relacionados con una entity

Los datos sobre una entidad se almacenan en su [components](/creator/content-creator-es/scenes-sdk7/arquitectura/entities-components.md). El SDK de Decentraland proporciona una serie de componentes base que gestionan diferentes aspectos de una entidad, como su posición, forma, material, etc. El engine sabe cómo interpretar la información en estos, y cambiará cómo se renderiza la entidad en consecuencia en cuanto cambien sus valores.

Si la lógica de tu scene requiere almacenar información sobre una entidad que no esté gestionada por los componentes predeterminados del SDK, entonces puedes crear un tipo personalizado de component en tu scene. Luego puedes crear [systems](/creator/content-creator-es/scenes-sdk7/arquitectura/systems.md) que comprueben los cambios en estos components y respondan en consecuencia.

## Acerca de la definición de components

Para definir un nuevo component, usa `engine.defineComponent`. Cada component necesita lo siguiente:

* Una **componentName**: Un identificador único de cadena que el SDK usa internamente para identificar este tipo de component. Puede ser cualquier cadena, siempre que sea única.
* Un **schema**: Una clase que define la estructura de datos que contiene el component.
* **valores predeterminados** *(opcional)*: Un objeto que contiene los valores predeterminados que se usarán para inicializar una copia del component, cuando no se proporcionen.

```ts
export const WheelSpinComponent = engine.defineComponent('wheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})
```

{% hint style="warning" %}
**📔 Nota**: Los Custom Components siempre deben escribirse fuera del `main()` función, en un archivo separado. Necesitan interpretarse antes de que `main()` se ejecuta. El lugar recomendado para esto es en una `/components` carpeta dentro de `/src`, cada uno en su propio archivo. De ese modo es más fácil reutilizarlos en proyectos futuros.
{% endhint %}

Una vez que hayas definido un custom component, puedes crear instancias de este component que hagan referencia a entidades en la scene. Cuando creas una instancia de un component, proporcionas valores para cada uno de los campos del schema del component. Los valores deben cumplir con los tipos declarados de cada campo.

```ts
// Crear entities
const wheel1 = engine.addEntity()
const wheel2 = engine.addEntity()

// Crear instancias del component
WheelSpinComponent.create(wheel1, {
	spinning: true,
	speed: 10,
})

WheelSpinComponent.create(wheel2, {
	spinning: false,
	speed: 0,
})
```

Cada entidad a la que se le añade el component instancia una nueva copia del component, que contiene datos específicos para esa entidad.

Tu custom component también puede realizar las otras funciones comunes que están disponibles en otros components:

```ts
// Obtener una instancia de solo lectura del component desde una entidad
const readOnlyInstance = MyCustomComponent.get(myEntity)

// Obtener una instancia mutable del component desde una entidad
const mutableInstance = MyCustomComponent.getMutable(myEntity)

// Eliminar la instancia del component de una entidad
MyCustomComponent.deleteFrom(myEntity)
```

## Acerca de componentName

Cada component debe tener un nombre o identificador único que lo diferencie internamente. No necesitarás usar este identificador interno en ningún otro lugar de tu código. Una buena práctica es usar el mismo nombre que asignas al component, pero empezando con una letra minúscula; sin embargo, lo que realmente importa es que este identificador sea único dentro del proyecto.

Al crear components que se compartirán como parte de una library, ten en cuenta que los nombres de los components de tu library no deben solaparse con ningún nombre de component del proyecto donde se use, ni con otras libraries que también use ese proyecto. Para evitar el riesgo de cualquier solapamiento, la mejor práctica recomendada es incluir el nombre de la library como parte de la `componentName` cadena. Puedes seguir esta fórmula: `${packageName}::${componentName}`. Por ejemplo, si construyes una`MyUtilities` library que incluye un `MoveEntity` component, establece el `componentName` de ese component en `MyUtilities::moveEntity`.

## Components como indicadores

Quizás quieras añadir un component que simplemente marque una entidad para diferenciarla de las demás, sin usarlo para almacenar ningún dato. Para ello, deja el schema como un objeto vacío.

Esto es especialmente útil al usar [consultas de components](/creator/content-creator-es/scenes-sdk7/arquitectura/querying-components.md). Un simple component indicador puede usarse para distinguir unas entidades de otras y evitar que el System itere sobre más entidades de las necesarias.

```ts
export const IsEnemyFlag = engine.defineComponent('isEnemyFlag', {})
```

Luego puedes crear un System que itere sobre todas las entidades con este component.

```ts
export function handleEnemies() {
	for (const [entity] of engine.getEntitiesWith(IsEnemyFlag)) {
		// hacer algo en cada entidad
	}
}

engine.addSystem(handleEnemies)
```

## Schemas de components

Un schema describe la estructura de los datos dentro de un component. Un component puede almacenar tantos campos como quieras; cada uno debe incluirse en la estructura del schema. El schema puede incluir tantos niveles de elementos anidados como necesites.

Cada campo del schema debe incluir una declaración de tipo. Solo puedes usar los tipos de schema especiales proporcionados por el SDK. Por ejemplo, usa el tipo `Schemas.Boolean` en lugar del tipo `boolean`. Escribe `Schemas.` y tu IDE mostrará todas las opciones disponibles.

```ts
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})
```

El ejemplo anterior define un component cuyo schema contiene dos valores, un `spinning` booleano y un `speed` número de punto flotante.

Puedes elegir crear el schema en línea al definir el component, o, para mayor legibilidad, puedes crearlo y luego referenciarlo.

```ts
// Opción 1: definición en línea
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})

// Opción 2: definir el schema y el component por separado

//// schema
const mySchema = {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
}

//// component
export const WheelSpinComponent = engine.defineComponent(
	'WheelSpinComponent',
	mySchema
)
```

{% hint style="info" %}
**💡 Consejo**: Al crear una instancia de un component, las opciones de autocompletado de VS Studio te sugerirán qué campos puedes añadir al component al presionar *Ctrl + Space*.
{% endhint %}

### Tipos predeterminados de Schema

Los siguientes tipos básicos están disponibles para usarse dentro de los campos de un schema:

* `Schemas.Boolean`
* `Schemas.Byte`
* `Schemas.Double`
* `Schemas.Float`
* `Schemas.Int`
* `Schemas.Int64`
* `Schemas.Number`
* `Schemas.Short`
* `Schemas.String`
* `Schemas.Entity`

También existen los siguientes tipos complejos. Cada uno incluye una serie de propiedades anidadas con valores numéricos.

* `Schemas.Vector3`
* `Schemas.Quaternion`
* `Schemas.Color3`
* `Schemas.Color4`

{% hint style="info" %}
**💡 Consejo**: Consulta [Tipos de geometría](/creator/content-creator-es/scenes-sdk7/fundamentos-del-contenido-3d/special-types.md) y [Tipos de color](/creator/content-creator-es/scenes-sdk7/fundamentos-del-contenido-3d/color-types.md) para más detalles sobre cómo son útiles estos tipos de datos.
{% endhint %}

Por ejemplo, puedes usar estos tipos de schema en un component como este para seguir el movimiento gradual de una entidad. Este component almacena una posición inicial y una final como valores Vector3, así como una velocidad y la fracción del recorrido completado como números float. Consulta [Mover Entities](/creator/content-creator-es/scenes-sdk7/fundamentos-del-contenido-3d/move-entities.md#move-between-two-points) la implementación completa de este ejemplo.

```ts
const MoveTransportData = {
	start: Schemas.Vector3,
	end: Schemas.Vector3,
	fraction: Schemas.Float,
	speed: Schemas.Float,
}

export const LerpTransformComponent = engine.defineComponent(
	'LerpTransformComponent',
	MoveTransportData
)
```

### Tipos de array

Para establecer el tipo de un campo como un array, usa `Schemas.Array()`. Pasa el tipo de los elementos del array como una propiedad.

```ts
const MySchema = {
	numberList: Schemas.Array(Schemas.Int),
}
```

Cuando leas de nuevo un campo array con `MyComponent.get()`, el array es de solo lectura. Los métodos que lo cambian en el lugar, como `.push()`, no están disponibles. Usa `MyComponent.getMutable()` cuando necesites cambiar el contenido.

### Campos opcionales

Usa `Schemas.Optional()` para permitir que un campo contenga un valor o `undefined`.

```ts
const MySchema = {
	playerId: Schemas.Optional(Schemas.String),
	score: Schemas.Optional(Schemas.Int),
}
```

Solo `undefined` cuenta como «no establecido». `false`, `0`, y `''` Los valores falsy como

### Tipos de schema anidados

Para establecer el tipo de un campo como un objeto, usa `Schemas.Map()`. Pasa el contenido de este objeto como una propiedad. Este objeto anidado es esencialmente un schema en sí mismo, anidado dentro del schema padre.

```ts
const MySchema = {
	simpleField: Schemas.Boolean,
	myComplexField: Schemas.Map({
		nestedField1: Schemas.Boolean,
		nestedField2: Schemas.Boolean,
	}),
}
```

Alternativamente, para que todo sea más legible y reutilizable, podrías lograr lo mismo definiendo el schema anidado por separado y luego referenciándolo al definir el schema padre.

```ts
const MyNestedSchema = Schemas.Map({
	nestedField1: Schemas.Boolean,
	nestedField2: Schemas.Boolean,
})

const MySchema = {
	simpleField: Schemas.Boolean,
	myComplexField: MyNestedSchema,
}
```

### Tipos de enum

Puedes establecer el tipo de un campo en un schema como un enum. Los enum facilitan seleccionar entre un número finito de opciones, proporcionando valores legibles para cada una.

Para establecer el tipo de un campo como un enum, primero debes definir el enum. Luego puedes referirte a él usando `Schemas.EnumNumber` o `Schemas.EnumString`, dependiendo del tipo de enum. Estas funciones reciben dos parámetros: el enum al que hacer referencia y un valor predeterminado para usar en este campo.

```ts
//// Enum de cadena

// Definir enum
enum Color {
	Red = 'red',
	Green = 'green',
	Pink = 'pink',
}

// Definir un component que usa este enum en un campo
const ColorComponent = engine.defineComponent('Color', {
	color: Schemas.EnumString<Color>(Color, Color.Red),
})

// Usar el component en una entidad
ColorComponent.create(engine.addEntity(), { color: Color.Green })

//// Enum de número

// Definir enum
enum CurveType {
	LINEAR,
	EASEIN,
	EASEOUT,
}

// Definir un component que usa este enum en un campo
const CurveComponent = engine.defineComponent('curveComponent', {
	curve: Schemas.EnumNumber<CurveType>(CurveType, CurveType.LINEAR),
})

// Usar el component en una entidad
CurveComponent.create(engine.addEntity(), { curve: CurveType.EASEIN })
```

### Tipos intercambiables

Puedes establecer el tipo de un campo en un schema para seguir un `oneOf` patrón, donde se pueden aceptar distintos tipos.

```ts
const MySchema = {
	myField: Schemas.OneOf({ type1: Schemas.Vector3, type2: Schemas.Quaternion }),
}

export const MyComponent = engine.defineComponent('MyComponent', MySchema)
```

Al crear una instancia del component, necesitas especificar el tipo seleccionado con una `$case`, por ejemplo:

```ts
MyComponent.create(myEntity, {
	myField: {
		$case: 'type1',
		value: Vector3.create(1, 1, 1),
	},
})
```

Dejar el campo sin establecer también es válido. Un campo OneOf sin establecer no tiene un valor `OneOf` y se lee de vuelta como un objeto vacío, `$case` y se lee de vuelta como un objeto vacío, `{}`.

### Componentes de un solo tipo

Un component no tiene por qué contener un objeto con varios campos. Para definir uno que contenga un solo valor, usa `engine.defineComponentFromSchema()` y pasa el tipo directamente:

```ts
// Un component que almacena un número por entidad
export const Score = engine.defineComponentFromSchema('my-scene::Score', Schemas.Int)

// Un component que almacena una lista de números por entidad
export const History = engine.defineComponentFromSchema(
	'my-scene::History',
	Schemas.Array(Schemas.Int)
)
```

Estos se comportan como cualquier otro component, incluso cuando el valor almacenado es falsy. Un `Score` de `0` es un component que existe y contiene `0`, no un component ausente.

## Valores predeterminados

Suele ser bueno tener valores predeterminados en tus components, para que no sea necesario establecer explícitamente cada valor cada vez que creas una nueva copia.

El `engine.defineComponent()` la función recibe un tercer argumento, que te permite pasar un objeto con valores para usar por defecto. Este objeto puede incluir algunos o todos los valores del schema. Los campos que no estén cubiertos por los predeterminados ni por los valores que proporciones al inicializar una copia del component se inicializan con un valor similar a cero, como `0`, `false`, o una cadena vacía, dependiendo del tipo.

```ts
// Definición

//// schema
const mySchema = {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
}

//// valores predeterminados
const myDefaultValues = {
	spinning: true,
	speed: 1,
}

//// component
export const WheelSpinComponent = engine.defineComponent(
	'WheelSpinComponent',
	mySchema,
	myDefaultValues
)

// Uso
export function main() {
	//// Crear entidades
	const wheel = engine.addEntity()
	const wheel2 = engine.addEntity()

	//// inicializar el component usando valores predeterminados
	WheelSpinComponent.create(wheel)

	//// inicializar el component con un valor personalizado, usando los valores predeterminados para cualquier otro
	WheelSpinComponent.create(wheel2, { speed: 5 })
}
```

El ejemplo anterior crea un `WheelSpinComponent` component que incluye tanto un schema como un conjunto de valores predeterminados para usar. Si luego inicializas una copia de este component sin especificar ningún valor, usará los establecidos en los valores predeterminados.

## Suscríbete a los cambios

Un caso de uso común es ejecutar una función solo cuando los datos de un cierto component cambian. Usa la [OnChange](/creator/content-creator-es/scenes-sdk7/arquitectura/subscribe-to-changes.md) función para evitar tener que definir un system y tener que comparar explícitamente los valores antiguos con los nuevos.

```ts
export function main() {
	//Crear entidad, etc

	WheelSpinComponent.onChange(myEntity, (componentData) => {
		if (!componentData) return
		console.log(componentData.speed)
		console.log(componentData.spinning)
	})
}
```

## Construyendo systems para usar un component

Con el component definido y añadido a las entidades de tu scene, puedes crear [Systems](/creator/content-creator-es/scenes-sdk7/arquitectura/systems.md) para realizar lógica, haciendo uso de estos datos almacenados en el component.

```ts
// definir component
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})

// Uso
export function main() {
	// Crear entities
	const wheel1 = engine.addEntity()
	const wheel2 = engine.addEntity()

	// Crear instancias del component
	WheelSpinComponent.create(wheel1, {
		spinning: true,
		speed: 10,
	})

	WheelSpinComponent.create(wheel2, {
		spinning: false,
		speed: 0,
	})
}

// Definir un System para iterar sobre estas entidades
export function spinSystem(dt: number) {
	// iterar sobre todas las entidades con un WheelSpinComponent
	for (const [entity, wheelSpin] of engine.getEntitiesWith(
		WheelSpinComponent
	)) {
		// solo hacer algo si spinning == true
		if (wheelSpin.spinning) {
			// obtener un component Transform mutable
			const transform = Transform.getMutable(entity)

			// actualizar el valor de rotación en consecuencia
			transform.rotation = Quaternion.multiply(
				transform.rotation,
				Quaternion.fromAngleAxis(dt * wheelSpin.speed, Vector3.Up())
			)
		}
	}
}

// Añadir el system al engine
engine.addSystem(spinSystem)
```

El ejemplo anterior define un System que itera sobre todas las entidades que incluyen el component personalizado `wheelSpinComponent`, y las rota ligeramente en cada tick del game loop. La cantidad de esta rotación es proporcional al `speed` valor almacenado en la instancia del component de cada entidad. El ejemplo hace uso de [consultas de components](/creator/content-creator-es/scenes-sdk7/arquitectura/querying-components.md) para obtener solo las entidades relevantes.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/custom-components.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
