# Components personalizados

Os dados sobre uma entity são armazenados no seu [components](/creator/content-creator-pt/scenes-sdk7/arquitetura/entities-components.md). O Decentraland SDK fornece uma série de base components que gerenciam diferentes aspectos de uma entity, como sua posição, shape, material, etc. O engine sabe como interpretar as informações nestes, e alterará como a entity é rendered de acordo assim que eles mudarem seus valores.

Se a lógica da sua scene exigir armazenar informações sobre uma entity que não são tratadas pelos components padrão do SDK, então você pode criar um tipo customizado de component na sua scene. Você pode então build [sistemas](/creator/content-creator-pt/scenes-sdk7/arquitetura/systems.md) que verificam mudanças nesses components e respondem adequadamente.

## Sobre definir components

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

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

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

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

Depois de definir um custom component, você pode criar instâncias deste component, que referenciam entities na scene. Quando você cria uma instância de um component, você fornece valores para cada um dos campos no schema do component. Os valores devem cumprir os tipos declarados de cada campo.

```ts
Se uma entity pai for escalada, todos os valores de position de suas filhas também serão escalados.
const wheel = engine.addEntity()
const wheel2 = engine.addEntity()

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

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

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

Seu custom component também pode realizar as outras funções comuns que estão disponíveis em outros components:

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

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

// Excluir a instância do component de uma entity
const readOnlyInstance MyCustomComponent.deleteFrom(myEntity)

```

## Sobre o componentName

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

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

## Components como flags

Talvez você queira adicionar um component que simplesmente marque uma entity para diferenciá-la de outras, sem usá-lo para armazenar nenhum dado. Para fazer isso, deixe o schema como um objeto vazio.

Isso é especialmente útil ao usar [querying components](/creator/content-creator-pt/scenes-sdk7/arquitetura/querying-components.md). Um simples flag component pode ser usado para distinguir entities de outras, e evitar que o system itere sobre mais entities do que o necessário.

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

Você pode então criar um system que itera sobre todas as entities com este component.

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

engine.addSystem(handleEnemies)
```

## Component Schemas

Um schema descreve a estrutura dos dados dentro de um component. Um component 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 forem necessários.

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`. Escreva `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 component cujo schema contém dois valores, um `spinning` boolean e um `speed` número de ponto flutuante.

Você pode escolher criar o schema inline ao definir o component, ou, para mais legibilidade, 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 component separadamente

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

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

{% hint style="info" %}
**💡 Dica**: Ao criar uma instância de um component, as opções de autocomplete do VS Studio sugerirão quais campos você pode adicionar ao component ao pressionar *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" %}
**💡 Dica**: Consulte [Tipos de geometria](/creator/content-creator-pt/scenes-sdk7/essenciais-de-conteudo-3d/special-types.md) e [Tipos de cor](/creator/content-creator-pt/scenes-sdk7/essenciais-de-conteudo-3d/color-types.md) para mais detalhes sobre como esses tipos de dados são úteis.
{% endhint %}

Por exemplo, você pode usar estes tipos de schema em um component assim para acompanhar o movimento gradual de uma entity. Este component armazena uma posição inicial e uma posição final como valores Vector3, além de uma velocidade e a fração do caminho concluído como números float. Veja [Mover entities](/creator/content-creator-pt/scenes-sdk7/essenciais-de-conteudo-3d/move-entities.md#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 de array

Para definir o tipo de um campo como um array, use `Schemas.Array()`. Passe o tipo dos elementos no 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. Este objeto aninhado é essencialmente um schema em si, aninhado dentro do schema pai.

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

Alternativamente, para manter tudo mais legível e reutilizável, você pode alcançar o mesmo definindo o schema aninhado separadamente e depois 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 de Enums

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

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

```ts
//// Enum de string

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

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

// Usar o component em uma entity
ColorComponent.create(engine.addEntity(), { color: Color.Green })

//// Enum numérico

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

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

// Usar o component em uma entity
CurveComponent.create(engine.addEntity(), { curve: CurveType.EASEIN })
```

### Tipos intercambiáveis

Você pode definir o tipo de um campo em um schema para seguir um padrão `oneOf` , no qual diferentes tipos 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 component, 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

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

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

```ts
// Definição

//// 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() {
	//// Criar entities
	const wheel = engine.addEntity()
	const wheel2 = engine.addEntity()

	//// inicializar component usando valores padrão
	WheelSpinComponent.create(wheel)

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

O exemplo acima cria um `WheelSpinComponent` component que inclui tanto um schema quanto um conjunto de valores padrão a serem usados. Se você então inicializar uma cópia deste component sem especificar nenhum valor, ele usará os definidos no padrão.

## Subscribe to changes

Um caso de uso comum é executar uma function apenas quando os dados de um determinado component mudam. Use a [OnChange](/creator/content-creator-pt/scenes-sdk7/arquitetura/subscribe-to-changes.md) function para evitar ter de definir um system e de comparar explicitamente valores antigos com novos valores.

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

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

## Construindo systems para usar um component

Com seu component definido e adicionado às entities na sua scene, você pode criar [Systems](https://github.com/decentraland/docs/blob/main/creator/deprecated/scenes/architecture/systems.md) para executar lógica, fazendo uso desses dados armazenados no component.

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

// Uso
export function main() {
	Se uma entity pai for escalada, todos os valores de position de suas filhas também serão escalados.
	const wheel1 = engine.addEntity()
	const wheel2 = engine.addEntity()

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

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

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

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

// Adicione o system ao engine
engine.addSystem(spinSystem)
```

O exemplo acima define um system que itera sobre todas as entities que incluem o custom `wheelSpinComponent`, e as gira levemente a cada tick do game loop. A quantidade dessa rotação é proporcional ao `speed` valor armazenado na instância do component de cada entity. O exemplo faz uso de [component queries](/creator/content-creator-pt/scenes-sdk7/arquitetura/querying-components.md) para obter apenas as entities relevantes.


---

# Agent Instructions: 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:

```
GET https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/arquitetura/custom-components.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
