# Components personalizados

Los datos sobre una entidad se almacenan en su [components](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/entities-components). 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 tan pronto como cambien sus valores.

Si la lógica de tu escena requiere almacenar información sobre una entidad que no está manejada por los componentes predeterminados del SDK, entonces puedes crear un tipo personalizado de componente en tu escena. Luego puedes construir [systems](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/systems) que comprueben cambios en estos componentes y respondan en consecuencia.

## Acerca de definir componentes

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

* Un **componentName**: Un identificador de cadena único que el SDK usa internamente para identificar este tipo de componente. Puede ser cualquier cadena, siempre que sea única.
* A **schema**: Una clase que define la estructura de datos contenida por el componente.
* **default values** *(opcional)*: Un objeto que contiene valores por defecto para usar al inicializar una copia del componente, cuando no se proporcionen.

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

{% hint style="warning" %}
**📔 Nota**: Los Componentes Personalizados deben siempre 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 en una `/components` carpeta dentro de `carpeta /src`, cada uno en su propio archivo. De esa manera es más fácil reutilizarlos en proyectos futuros.
{% endhint %}

Una vez que hayas definido un componente personalizado, puedes crear instancias de este componente, que referencien entidades en la escena. Cuando creas una instancia de un componente, proporcionas valores para cada uno de los campos del schema del componente. Los valores deben cumplir con los tipos declarados de cada campo.

```ts
// Crear entidades
const wheel = engine.addEntity()
const wheel2 = engine.addEntity()

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

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

Cada entidad que tiene el componente añadido instancia una nueva copia del componente, manteniendo datos específicos para esa entidad.

Tu componente personalizado también puede realizar las otras funciones comunes que están disponibles en otros componentes:

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

// Obtener una instancia mutable del componente de una entidad
const readOnlyInstance MyCustomComponent.getMutable(myEntity)

// Eliminar la instancia de un componente de una entidad
const readOnlyInstance MyCustomComponent.deleteFrom(myEntity)

```

## Acerca del componentName

Cada componente debe tener un nombre o identificador de componente ú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 componente, pero comenzando con una letra minúscula, aunque lo que realmente importa es que este identificador sea único dentro del proyecto.

Al crear componentes que serán compartidos como parte de una biblioteca, ten en cuenta que los nombres de componentes en tu biblioteca no deben coincidir con los nombres de componentes en el proyecto donde se usan, ni con otras bibliotecas que también use ese proyecto. Para evitar el riesgo de solapamientos, la práctica recomendada es incluir el nombre de la biblioteca como parte de la `componentName` cadena. Puedes seguir esta fórmula: `${packageName}::${componentName}`. Por ejemplo, si construyes una`MyUtilities` biblioteca que incluye un `MoveEntity` componente, establece el `componentName` de ese componente en `MyUtilities::moveEntity`.

## Componentes como marcas

Puede que quieras añadir un componente que simplemente marque una entidad para diferenciarla de otras, sin usarlo para almacenar datos. Para hacer esto, deja el schema como un objeto vacío.

Esto es especialmente útil cuando se usan [consultas de componentes](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/querying-components). Un componente de marca simple puede usarse para distinguir entidades de otras, y evitar que el sistema itere sobre más entidades de las necesarias.

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

Luego puedes crear un sistema que itere sobre todas las entidades con este componente.

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

engine.addSystem(handleEnemies)
```

## Schemas de Componentes

Un schema describe la estructura de los datos dentro de un componente. Un componente 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 en el 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 componente cuyo schema contiene dos valores, un `spinning` booleano y un `speed` número de punto flotante.

Puedes optar por crear el schema en línea al definir el componente, 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 schema y componente por separado

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

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

{% hint style="info" %}
**💡 Tip**: Al crear una instancia de un componente, las opciones de autocompletado de VS Studio sugerirán qué campos puedes agregar al componente presionando *Ctrl + Space*.
{% endhint %}

### Tipos de Schema por defecto

Los siguientes tipos básicos están disponibles para usar 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" %}
**💡 Tip**: Ver [Tipos de geometría](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/conceptos-esenciales-de-contenido-3d/special-types) y [Tipos de color](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/conceptos-esenciales-de-contenido-3d/color-types) para más detalles sobre cómo estos tipos de datos son útiles.
{% endhint %}

Por ejemplo, puedes usar estos tipos de schema en un componente como este para rastrear el movimiento gradual de una entidad. Este componente almacena una posición inicial y una final como valores Vector3, además de una velocidad y una fracción del recorrido completado como números float. Ver [Mover entidades](https://docs.decentraland.org/creator/content-creator-es/conceptos-esenciales-de-contenido-3d/move-entities#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 arreglos

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

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

### 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í, anidado dentro del schema padre.

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

Alternativamente, para mantener las cosas más legibles y reutilizables, 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 enum

Puedes establecer el tipo de un campo 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 tipo de un campo como enum, primero debes definir el enum. Luego puedes referirte a él usando `Schemas.EnumNumber` o `Schemas.EnumString`, dependiendo del tipo de enum. Debes pasar el enum a referenciar entre `<>`, así como el tipo como parámetro (ya sea `Schemas.Int` para enums numéricos, o `Schemas.String` para enums de cadena). También debes pasar un valor por defecto para usar en este campo.

```ts
//// Enum de string

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

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

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

//// Enum numérico

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

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

// Usar componente 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 diferentes 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 componente, necesitas especificar el tipo seleccionado con un `$case`, por ejemplo:

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

## Valores por defecto

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

El `engine.defineComponent()` la función 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 valores que no se proporcionen en los defaults deberán siempre proporcionarse al inicializar una copia del componente.

```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 entidades
	const wheel = engine.addEntity()
	const wheel2 = engine.addEntity()

	//// inicializar componente usando valores por defecto
	WheelSpinComponent.create(wheel)

	//// inicializar componente con un valor personalizado, usando por defecto los demás
	WheelSpinComponent.create(wheel2, { speed: 5 })
}
```

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

## Suscribirse a cambios

Un caso de uso común es ejecutar una función solo en caso de que los datos en cierto componente cambien. Usa la [OnChange](https://docs.decentraland.org/creator/content-creator-es/scenes-sdk7/arquitectura/subscribe-to-changes) function para evitar tener que definir un system y tener que comparar explícitamente valores antiguos con 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 componente

Con tu componente definido y añadido a entidades en tu escena, puedes crear [Sistemas](https://github.com/decentraland/docs/blob/main/creator/deprecated/scenes/architecture/systems.md) para realizar lógica, haciendo uso de estos datos almacenados en el componente.

```ts
// definir componente
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 componente
	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 componente 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 sistema al engine
engine.addSystem(spinSystem)
```

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