> 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).

# Components personalizados

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

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

Si la lógica de tu scene requiere almacenar información sobre una entity que no está gestionada por los components 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 de string único que el SDK usa internamente para identificar este tipo de component. Puede ser cualquier string, siempre que sea único.
* A **schema**: Una clase que define la estructura de datos que contiene el component.
* **valores predeterminados** *(opcional)*: Un objeto que contiene valores predeterminados para usar al inicializar una copia del component, cuando no se proporcionan.

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

{% hint style="warning" %}
**📔 Nota**: Los Custom Components siempre deben escribirse fuera de la `main()` function, en un archivo separado. Necesitan ser interpretados antes de que `main()` se ejecute. El lugar recomendado para esto es una `/components` carpeta dentro de `/src`, cada uno en su propio archivo. Así 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 entities en la scene. Cuando creas una instancia de un component, proporcionas valores para cada uno de los fields del schema del component. Los valores deben cumplir con los tipos declarados de cada field.

```ts
// Crear entidades
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 entity a la que se le añade el component instancia una nueva copia del component, que contiene datos específicos para esa entity.

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 entity
const readOnlyInstance = MyCustomComponent.get(myEntity)

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

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

## Acerca de componentName

Cada component debe tener un nombre o identificador de component ú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, aunque lo que realmente importa es que este identificador sea único dentro del proyecto.

Cuando crees 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` string. 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 a `MyUtilities::moveEntity`.

## Components como flags

Puede que quieras añadir un component que simplemente marque una entity para diferenciarla de otras, sin usarlo para almacenar ningún dato. Para hacerlo, 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 de flag puede usarse para distinguir entities de otras, y evitar que el system itere sobre más entities de las necesarias.

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

Luego puedes crear un system que itere sobre todas las entities con este component.

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

engine.addSystem(handleEnemies)
```

## Schemas de components

Un schema describe la estructura de los datos dentro de un component. Un component puede almacenar tantos fields como quieras, y cada uno debe estar incluido en la estructura del schema. El schema puede incluir tantos niveles de elementos anidados como necesites.

Cada field del schema debe incluir una declaración de tipo. Solo puedes usar los tipos especiales de schema proporcionados por el SDK. Por ejemplo, usa el type `Schemas.Boolean` en lugar del type `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` boolean y un `speed` número de punto flotante.

Puedes elegir crear el schema inline al definir el component, o para mayor legibilidad puedes crearlo y luego referenciarlo.

```ts
// Opción 1: definición inline
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é fields puedes añadir al component al presionar *Ctrl + Space*.
{% endhint %}

### Tipos de Schema predeterminados

Los siguientes tipos básicos están disponibles para usar dentro de los fields 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/conceptos-basicos-del-contenido-3d/special-types.md) y [Tipos de color](/creator/content-creator-es/scenes-sdk7/conceptos-basicos-del-contenido-3d/color-types.md) para más detalles sobre cómo resultan ú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 entity. Este component almacena una posición inicial y una final como valores Vector3, así como una speed y una fracción del recorrido completado como números flotantes. Consulta [Mover entidades](/creator/content-creator-es/scenes-sdk7/conceptos-basicos-del-contenido-3d/move-entities.md#move-between-two-points) para 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 type de un field como un array, usa `Schemas.Array()`. Pasa el type de los elementos del array como una propiedad.

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

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

### Campos opcionales

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

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

Solo `undefined` cuenta como "no establecido". Los valores falsy como `false`, `0`, y `''` se almacenan y se leen exactamente tal como se escriben.

### Tipos de schema anidados

Para establecer el type de un field 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,
	}),
}
```

Como alternativa, para que todo sea más legible y reutilizable, podrías conseguir 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 type de un field en un schema como un enum. Los enums facilitan seleccionar entre un número finito de opciones, proporcionando valores legibles para cada una.

Para establecer el type de un field como un enum, primero debes definir el enum. Luego puedes referenciarlo usando `Schemas.EnumNumber` ni `Schemas.EnumString`, dependiendo del type de enum. Estas funciones toman dos parámetros: el enum a referenciar y un valor predeterminado para usar en este field.

```ts
//// Enum de string

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

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

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

//// Enum de número

// Define el enum
enum CurveType {
	LINEAR,
	EASEIN,
	EASEOUT,
}

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

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

### Tipos intercambiables

Puedes establecer el type de un field en un schema para seguir un patrón oneOf, donde se pueden aceptar distintos tipos. `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 type seleccionado con un `$case`, por ejemplo:

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

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

### Components de un solo tipo

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

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

// Un component que contiene una lista de números por entity
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

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

La `engine.defineComponent()` function toma 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 fields que no estén cubiertos por los defaults o por los valores que proporciones al inicializar una copia del component se inicializan con un valor parecido a cero, como `0`, `false`, o una cadena vacía, dependiendo del type.

```ts
// Definición

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

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

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

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

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

	//// inicializar el component con un valor personalizado, usando el predeterminado para los demás
	WheelSpinComponent.create(wheel2, { speed: 5 })
}
```

El ejemplo anterior crea un component WheelSpinComponent 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 el predeterminado. `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 el predeterminado.

## Suscribirse a los cambios

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

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

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

## Creando systems para usar un component

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

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

// Uso
export function main() {
	// Crear entidades
	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,
	})
}

// Define un system para iterar sobre estas entities
export function spinSystem(dt: number) {
	// iterar sobre todas las entities 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 entities que incluyen el custom `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 entity. El ejemplo hace uso de [consultas de components](/creator/content-creator-es/scenes-sdk7/arquitectura/querying-components.md) para obtener solo las entities 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
