# Components Personalizados

Dados sobre uma entidade são armazenados em seu [components](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/entities-components). O SDK do Decentraland fornece uma série de componentes base que gerenciam diferentes aspectos de uma entidade, como sua posição, forma, material, etc. O engine sabe como interpretar a informação neles e mudará a forma como a entidade é renderizada assim que seus valores forem alterados.

Se a lógica da sua cena requer armazenar informações sobre uma entidade que não são tratadas pelos componentes padrão do SDK, então você pode criar um tipo customizado de componente na sua cena. Você pode então construir [systems](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/systems) que verificam mudanças nesses componentes e respondem adequadamente.

## Sobre definir componentes

Para definir um novo componente, use `engine.defineComponent`. Cada componente precisa do seguinte:

* Um **componentName**: Um identificador de string único que o SDK usa internamente para identificar esse tipo de componente. Pode ser qualquer string, desde que seja única.
* A **schema**: Uma classe que define a estrutura de dados mantida pelo componente.
* **default values** *(opcional)*: Um objeto contendo valores padrão para usar ao inicializar uma cópia do componente, quando estes não são fornecidos.

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

{% hint style="warning" %}
**📔 Nota**: Componentes Customizados devem sempre ser escritos fora da `main()` function, em um arquivo separado. Eles precisam ser interpretados antes de `main()` ser executado. O local recomendado para isso é em uma `/components` pasta dentro de `/src`, cada um em seu próprio arquivo. Dessa forma é mais fácil reutilizá-los em projetos futuros.
{% endhint %}

Uma vez que você definiu um componente customizado, você pode criar instâncias desse componente, que referenciam entidades na cena. Quando você cria uma instância de um componente, você fornece valores para cada um dos campos no schema do componente. Os valores devem obedecer aos tipos declarados de cada campo.

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

// Crie instâncias do componente
WheelSpinComponent.create(wheel1, {
	spinning: true,
	speed: 10,
})

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

Cada entidade que tem o componente adicionado instancia uma nova cópia do componente, contendo dados específicos para essa entidade.

Seu componente customizado também pode executar as outras funções comuns disponíveis em outros componentes:

```ts
// Buscar uma instância somente leitura do componente de uma entidade
const readOnlyInstance MyCustomComponent.get(myEntity)

// Buscar uma instância mutável do componente de uma entidade
const readOnlyInstance MyCustomComponent.getMutable(myEntity)

// Deletar a instância do componente de uma entidade
const readOnlyInstance MyCustomComponent.deleteFrom(myEntity)

```

## Sobre o componentName

Cada componente deve ter um nome ou identificador de componente único, que o diferencie internamente. Você não precisará usar esse identificador interno em nenhuma outra parte do seu código. Uma boa prática é usar o mesmo nome que você atribui ao componente, mas começando com uma letra minúscula, porém o que realmente importa é que esse identificador seja único dentro do projeto.

Ao criar componentes que serão compartilhados como parte de uma biblioteca, tenha em mente que os nomes dos componentes na sua biblioteca não devem se sobrepor a quaisquer nomes de componentes no projeto onde ela for usada, ou em outras bibliotecas que também sejam usadas por esse projeto. Para evitar o risco de qualquer sobreposição, a prática recomendada é incluir o nome da biblioteca como parte da `componentName` string. Você pode seguir esta fórmula: `${packageName}::${componentName}`. Por exemplo se você constrói uma`MyUtilities` biblioteca que inclui um `MoveEntity` componente, defina o `componentName` desse componente como `MyUtilities::moveEntity`.

## Componentes como flags

Você pode querer adicionar um componente que simplesmente marca (flag) uma entidade para diferenciá-la das outras, sem usá-lo para armazenar qualquer dado. Para fazer isso, deixe o schema como um objeto vazio.

Isso é especialmente útil quando se usam [consultas de componentes](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/querying-components). Um componente de flag simples pode ser usado para distinguir entidades das outras, e evitar que o sistema itere sobre mais entidades do que o necessário.

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

Você então pode criar um sistema que itere sobre todas as entidades com esse componente.

```ts
export function handleEnemies() {
	for (const [entity] of engine.getEntitiesWith(IsEnemyFlag)) {
		// faça algo em cada entidade
	}
}

engine.addSystem(handleEnemies)
```

## Schemas de Componentes

Um schema descreve a estrutura dos dados dentro de um componente. Um componente pode armazenar quantos campos você quiser, cada um deve ser incluído na estrutura do schema. O schema pode incluir quantos níveis de itens aninhados você precisar.

Cada campo no schema deve incluir uma declaração de tipo. Você só pode usar os tipos especiais de schema fornecidos pelo SDK. Por exemplo, use o tipo `Schemas.Boolean` em vez do tipo `boolean`. Digite `Schemas.` e seu IDE exibirá todas as opções disponíveis.

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

O exemplo acima define um componente cujo schema contém dois valores, um `spinning` booleano e um `Altere a velocidade com que uma animação é reproduzida alterando a propriedade` número de ponto flutuante.

Você pode optar por criar o schema inline enquanto define o componente, ou para maior legibilidade você pode criá-lo e então referenciá-lo.

```ts
// Opção 1: Definição inline
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})

// Opção 2: definir schema e componente separadamente

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

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

{% hint style="info" %}
**💡 Tip**: Ao criar uma instância de um componente, as opções de autocompletar do VS Studio sugerirão quais campos você pode adicionar ao componente pressionando *Ctrl + Space*.
{% endhint %}

### Tipos de Schema padrão

Os seguintes tipos básicos estão disponíveis para uso dentro dos campos de um schema:

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

Os seguintes tipos complexos também existem. Cada um inclui uma série de propriedades aninhadas com valores numéricos.

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

{% hint style="info" %}
**💡 Tip**: Veja [Tipos de geometria](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/essenciais-de-conteudo-3d/special-types) e [Tipos de cor](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/essenciais-de-conteudo-3d/color-types) para mais detalhes sobre como esses tipos de dados são úteis.
{% endhint %}

Por exemplo, você pode usar esses tipos de schema em um componente como este para rastrear o movimento gradual de uma entidade. Este componente armazena uma posição inicial e uma final como valores Vector3, assim como uma velocidade e fração do caminho completado como números float. Veja [Mover entidades](https://docs.decentraland.org/creator/content-creator-pt/essenciais-de-conteudo-3d/move-entities#move-between-two-points) para a implementação completa deste exemplo.

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

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

### Tipos Array

Para definir o tipo de um campo como um array, use `Schemas.Array()`. Passe o tipo dos elementos do array como uma propriedade.

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

### Tipos de schema aninhados

Para definir o tipo de um campo como um objeto, use `Schemas.Map()`. Passe o conteúdo deste objeto como uma propriedade. Esse objeto aninhado é essencialmente um schema por si só, aninhado dentro do schema pai.

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

Alternativamente, para manter as coisas mais legíveis e reutilizáveis, você pode alcançar o mesmo definindo o schema aninhado separadamente e então referenciando-o ao definir o schema pai.

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

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

### Tipos Enums

Você pode definir o tipo de um campo em um schema como um enum. Enums facilitam selecionar entre um número finito de opções, fornecendo valores legíveis para humanos para cada uma.

Para definir o tipo de um campo como um enum, você deve primeiro definir o enum. Então você pode referenciá-lo usando `Schemas.EnumNumber` ou `Schemas.EnumString`, dependendo do tipo de enum. Você deve passar o enum para referenciar entre `<>`, assim como o tipo como parâmetro (ou `Schemas.Int` para enums numéricos, ou `Schemas.String` para enums de string). Você também deve passar um valor padrão para usar para esse campo.

```ts
//// Enum de string

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

// Defina um componente que usa esse enum em um campo
const ColorComponent = engine.defineComponent('Color', {
	color: Schemas.EnumString<Color>(Color, Color.Red),
})

// Use o componente em uma entidade
ColorComponent.create(engine.addEntity(), { color: Color.Green })

//// Enum numérico

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

// Defina um componente que usa esse enum em um campo
const CurveComponent = engine.defineComponent('curveComponent', {
	curve: Schemas.EnumString<CurveType>(CurveType, CurveType.LINEAR),
})

// Use o componente em uma entidade
CurveComponent.create(engine.addEntity(), { curve: CurveType.EASEIN })
```

### Tipos intercambiáveis

Você pode definir o tipo de um campo em um schema para seguir um `oneOf` padrão, onde tipos diferentes podem ser aceitos.

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

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

Ao criar uma instância do componente, você precisa especificar o tipo selecionado com um `$case`, por exemplo:

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

## Valores padrão

É frequentemente bom ter valores padrão em seus componentes, para que não seja necessário definir explicitamente cada valor toda vez que você criar uma nova cópia.

O `engine.defineComponent()` função recebe um terceiro argumento, que permite passar um objeto com valores para usar por padrão. Esse objeto pode incluir alguns ou todos os valores do schema. Valores que não são fornecidos nos defaults precisarão sempre ser fornecidos ao inicializar uma cópia do componente.

```ts
// Definição

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

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

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

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

	//// inicialize o componente usando valores padrão
	WheelSpinComponent.create(wheel)

	//// inicialize o componente com um valor customizado, usando padrão para os demais
	WheelSpinComponent.create(wheel2, { speed: 5 })
}
```

O exemplo acima cria um `WheelSpinComponent` componente que inclui tanto um schema quanto um conjunto de valores padrão para usar. Se você então inicializar uma cópia deste componente sem especificar quaisquer valores, ele usará os definidos no default.

## Inscrever-se em mudanças

Um caso de uso comum é executar uma função apenas caso os dados em certo componente mudem. Use a [OnChange](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/subscribe-to-changes) function para evitar ter que definir um system e ter que comparar explicitamente valores antigos com valores novos.

```ts
export function main() {
	//Crie entidade, etc

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

## Construindo systems para usar um componente

Com seu componente definido e adicionado a entidades na sua cena, você pode criar [Sistemas](https://github.com/decentraland/docs/blob/main/creator/deprecated/scenes/architecture/systems.md) para realizar lógica, fazendo uso desses dados armazenados no componente.

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

// Uso
export function main() {
	// Crie entidades
	const wheel1 = engine.addEntity()
	const wheel2 = engine.addEntity()

	// Crie instâncias do componente
	WheelSpinComponent.create(wheel1, {
		spinning: true,
		speed: 10,
	})

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

// Defina um system para iterar sobre essas entidades
export function spinSystem(dt: number) {
	// iterar sobre todas as entidades com um WheelSpinComponent
	for (const [entity, wheelSpin] of engine.getEntitiesWith(
		WheelSpinComponent
	)) {
		// só faça algo se spinning == true
		if (wheelSpin.spinning) {
			// buscar um componente Transform mutável
			const transform = Transform.getMutable(entity)

			// atualize o valor de rotação adequadamente
			transform.rotation = Quaternion.multiply(
				transform.rotation,
				Quaternion.fromAngleAxis(dt * wheelSpin.speed, Vector3.Up())
			)
		}
	}
}

// Adicionar sistema ao engine
engine.addSystem(spinSystem)
```

O exemplo acima define um system que itera sobre todas as entidades que incluem o custom `wheelSpinComponent`, e as rotaciona levemente a cada tick do loop do jogo. A quantidade dessa rotação é proporcional ao `Altere a velocidade com que uma animação é reproduzida alterando a propriedade` valor armazenado na instância do componente de cada entidade. O exemplo faz uso de [consultas de componente](https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/querying-components) para obter apenas as entidades relevantes.
