> 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-ko/sdk7/architecture/custom-components.md).

# 사용자 지정 컴포넌트

엔티티와 관련된 특정 데이터를 처리하는 사용자 지정 컴포넌트를 만드세요

엔티티에 대한 데이터는 그 엔티티의 [컴포넌트](/creator/content-creator-ko/sdk7/architecture/entities-components.md). Decentraland SDK는 엔티티의 위치, 모양, 재질 등 다양한 측면을 관리하는 일련의 기본 컴포넌트를 제공합니다. 엔진은 이들에 담긴 정보를 해석하는 방법을 알고 있으며, 값이 변경되면 즉시 그에 맞게 엔티티의 렌더링 방식을 변경합니다.

씬의 로직상 SDK의 기본 컴포넌트가 처리하지 않는 엔티티의 정보를 저장해야 한다면, 씬에 사용자 정의 컴포넌트 타입을 만들 수 있습니다. 그런 다음 [시스템](/creator/content-creator-ko/sdk7/architecture/systems.md) 이 컴포넌트들의 변경 사항을 확인하고 그에 맞게 반응하는 시스템을 만들 수 있습니다.

## 컴포넌트 정의하기에 관하여

새 컴포넌트를 정의하려면 `engine.defineComponent`. 각 컴포넌트에는 다음이 필요합니다:

* 다음은 **componentName**: SDK가 내부적으로 이 컴포넌트 타입을 식별하는 데 사용하는 고유한 문자열 식별자입니다. 고유하기만 하면 어떤 문자열이든 될 수 있습니다.
* 하나의 **schema**: 컴포넌트가 담는 데이터 구조를 정의하는 클래스입니다.
* **기본값** *(선택 사항)*: 제공되지 않았을 때 컴포넌트의 복사본을 초기화하는 데 사용할 기본값을 담은 객체입니다.

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

{% hint style="warning" %}
**📔 참고**: 사용자 정의 컴포넌트는 항상 `함수 내부,` 함수 밖의 별도 파일에 작성해야 합니다. 다른 모든 것보다 먼저 해석되어야 `함수 내부,` 바깥에 작성해야 합니다. 이를 위한 권장 위치는 `/components` 폴더 내부의 `/src`이며, 각 컴포넌트는 별도 파일에 두는 것입니다. 이렇게 하면 나중에 다른 프로젝트에서 재사용하기가 더 쉽습니다.
{% endhint %}

사용자 정의 컴포넌트를 정의한 뒤에는, 씬의 엔티티를 참조하는 이 컴포넌트의 인스턴스를 만들 수 있습니다. 컴포넌트의 인스턴스를 만들 때는 컴포넌트의 schema에 있는 각 필드에 값을 제공합니다. 값은 각 필드에 대해 선언된 타입을 준수해야 합니다.

```ts
// 엔티티 생성
const wheel1 = engine.addEntity()
const wheel2 = engine.addEntity()

// 컴포넌트 인스턴스 생성
WheelSpinComponent.create(wheel1, {
	spinning: true,
	speed: 10,
})

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

컴포넌트가 추가된 각 엔티티는 그 엔티티에 특정한 데이터를 담는 컴포넌트의 새 복사본을 인스턴스화합니다.

사용자 정의 컴포넌트는 다른 컴포넌트에서 사용할 수 있는 다른 일반적인 기능도 수행할 수 있습니다:

```ts
// 엔티티에서 컴포넌트의 읽기 전용 인스턴스 가져오기
const readOnlyInstance = MyCustomComponent.get(myEntity)

// 엔티티에서 컴포넌트의 변경 가능한 인스턴스 가져오기
const mutableInstance = MyCustomComponent.getMutable(myEntity)

// 엔티티의 컴포넌트 인스턴스 삭제
MyCustomComponent.deleteFrom(myEntity)
```

## componentName에 관하여

각 컴포넌트에는 내부적으로 구분할 수 있도록 고유한 컴포넌트 이름 또는 식별자가 있어야 합니다. 이 내부 식별자는 코드의 다른 어느 곳에서도 사용할 필요가 없습니다. 좋은 관행은 컴포넌트에 부여한 이름과 같되 첫 글자만 소문자로 시작하는 것을 쓰는 것입니다. 하지만 정말 중요한 것은 이 식별자가 프로젝트 안에서 고유해야 한다는 점입니다.

라이브러리의 일부로 공유될 컴포넌트를 만들 때는, 라이브러리의 컴포넌트 이름이 사용되는 프로젝트의 다른 컴포넌트 이름이나 그 프로젝트에서 함께 사용되는 다른 라이브러리의 컴포넌트 이름과 겹치지 않도록 주의해야 합니다. 겹칠 위험을 피하려면, 권장되는 가장 좋은 방법은 라이브러리 이름을 `componentName` 문자열의 일부로 포함하는 것입니다. 다음 공식에 따를 수 있습니다: `${packageName}::${componentName}`. 예를 들어,`MyUtilities` 라는 라이브러리를 만들고 그 안에 `MoveEntity` 컴포넌트를 포함한다면, 해당 컴포넌트의 `componentName` 값을 `MyUtilities::moveEntity`.

## 플래그로서의 컴포넌트

데이터를 저장하는 데 사용하지 않고, 단순히 엔티티를 다른 엔티티와 구분하기 위한 표시만 추가하는 컴포넌트를 만들고 싶을 수 있습니다. 이를 위해서는 schema를 빈 객체로 두면 됩니다.

특히 [컴포넌트 쿼리](/creator/content-creator-ko/sdk7/architecture/querying-components.md)를 사용할 때 유용합니다. 간단한 플래그 컴포넌트는 엔티티를 다른 엔티티와 구분하고, 시스템이 필요한 것보다 더 많은 엔티티를 반복 처리하지 않도록 하는 데 사용할 수 있습니다.

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

그런 다음 이 컴포넌트가 있는 모든 엔티티를 순회하는 시스템을 만들 수 있습니다.

```ts
export function handleEnemies() {
	for (const [entity] of engine.getEntitiesWith(IsEnemyFlag)) {
		// 각 엔티티에서 무언가 수행
	}
}

engine.addSystem(handleEnemies)
```

## 컴포넌트 스키마

스키마는 컴포넌트 안의 데이터 구조를 설명합니다. 컴포넌트에는 원하는 만큼 많은 필드를 저장할 수 있으며, 각 필드는 스키마 구조에 포함되어야 합니다. 스키마에는 필요한 만큼 중첩 항목의 레벨을 포함할 수 있습니다.

스키마의 모든 필드에는 타입 선언이 포함되어야 합니다. SDK가 제공하는 특수한 스키마 타입만 사용할 수 있습니다. 예를 들어, 타입 `Schemas.Boolean` 대신 type `불리언`. Schemas.를 입력하세요. `Schemas.` 를 입력하면 IDE가 사용 가능한 모든 옵션을 표시합니다.

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

위 예제는 schema가 두 값을 담는 컴포넌트를 정의합니다. 하나는 `spinning` 불리언이고, 다른 하나는 `속도` 부동소수점 숫자입니다.

컴포넌트를 정의할 때 schema를 인라인으로 만들 수도 있고, 가독성을 높이기 위해 먼저 만든 다음 참조할 수도 있습니다.

```ts
// 옵션 1: 인라인 정의
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})

// 옵션 2: schema와 컴포넌트를 분리해 정의

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

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

{% hint style="info" %}
**💡 팁**: 컴포넌트 인스턴스를 만들 때, VS Studio 자동 완성 옵션은 *Ctrl + Space*.
{% endhint %}

### 기본 Schema 타입

schema의 필드 안에서 사용할 수 있는 기본 타입은 다음과 같습니다:

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

다음의 복합 타입도 존재합니다. 각 타입은 숫자 값이 있는 여러 중첩 속성을 포함합니다.

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

{% hint style="info" %}
**💡 팁**: 참고 [기하 도형 유형](/creator/content-creator-ko/sdk7/3d/special-types.md) 및 [색상 타입](/creator/content-creator-ko/sdk7/3d/color-types.md) 이러한 데이터 타입이 어떻게 유용한지에 대한 자세한 내용은
{% endhint %}

예를 들어, 엔티티의 점진적인 이동을 추적하기 위해 컴포넌트에서 이런 schema 타입을 사용할 수 있습니다. 이 컴포넌트는 초기 위치와 최종 위치를 Vector3 값으로 저장하고, 속도와 이동 완료 비율을 float 숫자로 저장합니다. 이 예제의 전체 구현은 [엔티티 이동](/creator/content-creator-ko/sdk7/3d/move-entities.md#move-between-two-points) 를 참조하세요.

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

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

### 배열 타입

필드의 타입을 배열로 설정하려면 `Schemas.Array()`를 사용하세요. 배열 요소의 타입은 속성으로 전달합니다.

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

배열 필드를 다시 읽을 때 `MyComponent.get()`를 사용하면 배열은 읽기 전용입니다. 예를 들어 `.push()`처럼 제자리에서 변경하는 메서드는 사용할 수 없습니다. 내용을 변경해야 할 때는 `MyComponent.getMutable()` 를 사용하세요.

### 선택적 필드

사용: `Schemas.Optional()` 를 사용해 필드가 값 또는 `undefined`.

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

만 가질 수 있도록 허용합니다. `undefined` 만이 "설정되지 않음"으로 간주됩니다.  `false`, `0`, 그리고 `''` 같은 falsy 값은 그대로 저장되고 다시 읽어도 작성한 그대로입니다.

### 중첩된 schema 타입

필드의 타입을 객체로 설정하려면 `Schemas.Map()`를 사용하세요. 이 객체의 내용을 속성으로 전달합니다. 이 중첩 객체는 본질적으로 그 자체가 schema이며, 상위 schema 안에 중첩된 것입니다.

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

또는, 더 읽기 쉽고 재사용하기 쉽게 하려면 중첩 schema를 별도로 정의한 다음 상위 schema를 정의할 때 이를 참조하면 같은 결과를 얻을 수 있습니다.

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

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

### 열거형 타입

schema의 필드 타입을 enum으로 설정할 수 있습니다. enum은 유한한 수의 옵션 중에서 쉽게 선택할 수 있게 해 주며, 각 값에 사람이 읽기 쉬운 값을 제공합니다.

필드의 타입을 enum으로 설정하려면 먼저 enum을 정의해야 합니다. 그런 다음 다음을 사용해 참조할 수 있습니다 `Schemas.EnumNumber` 또는 `Schemas.EnumString`, enum의 타입에 따라 달라집니다. 이 함수들은 두 개의 매개변수를 받습니다. 참조할 enum과 이 필드에 사용할 기본값입니다.

```ts
//// 문자열 enum

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

// 이 enum을 필드에서 사용하는 컴포넌트 정의
const ColorComponent = engine.defineComponent('Color', {
	color: Schemas.EnumString<Color>(Color, Color.Red),
})

// 엔티티에 컴포넌트 사용
ColorComponent.create(engine.addEntity(), { color: Color.Green })

//// 숫자 enum

// enum 정의
enum CurveType {
	LINEAR,
	EASEIN,
	EASEOUT,
}

// 이 enum을 필드에서 사용하는 컴포넌트 정의
const CurveComponent = engine.defineComponent('curveComponent', {
	curve: Schemas.EnumNumber<CurveType>(CurveType, CurveType.LINEAR),
})

// 엔티티에 컴포넌트 사용
CurveComponent.create(engine.addEntity(), { curve: CurveType.EASEIN })
```

### 호환 가능한 타입

schema의 필드 타입을 다음 패턴을 따르도록 설정할 수 있습니다 `oneOf` 패턴으로, 서로 다른 타입을 허용할 수 있습니다.

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

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

컴포넌트 인스턴스를 만들 때는 선택한 타입을 `$case`로 지정해야 합니다. 예를 들면:

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

필드를 설정하지 않은 채로 두는 것도 유효합니다. 설정되지 않은 `OneOf` 필드에는 `$case` 가 없으며, 빈 객체로 읽힙니다, `{}`.

### 단일 타입의 컴포넌트

컴포넌트가 여러 필드를 가진 객체를 담을 필요는 없습니다. 단일 값을 담는 컴포넌트를 정의하려면 `engine.defineComponentFromSchema()` 를 사용하고 타입을 직접 전달하세요:

```ts
// 엔티티당 하나의 숫자를 담는 컴포넌트
export const Score = engine.defineComponentFromSchema('my-scene::Score', Schemas.Int)

// 엔티티당 숫자 목록을 담는 컴포넌트
export const History = engine.defineComponentFromSchema(
	'my-scene::History',
	Schemas.Array(Schemas.Int)
)
```

이들은 저장된 값이 falsy일 때도 다른 컴포넌트와 똑같이 동작합니다.  `Score` 의 `0` 는 존재하며 `0`를 담고 있는 컴포넌트이지, 누락된 컴포넌트가 아닙니다.

## 기본값

컴포넌트에 기본값을 두는 것이 좋은 경우가 많습니다. 그래야 새 복사본을 만들 때마다 각 값을 명시적으로 설정할 필요가 없습니다.

그 `engine.defineComponent()` 함수는 세 번째 인자를 받으며, 여기에서 기본으로 사용할 값을 담은 객체를 전달할 수 있습니다. 이 객체에는 schema의 값 전부 또는 일부를 포함할 수 있습니다. 기본값이나 컴포넌트 복사본을 초기화할 때 제공한 값으로도 다루어지지 않은 필드는 `0`, `false`와 같은 제로에 가까운 값이나 빈 문자열로, 타입에 따라 초기화됩니다.

```ts
// 정의

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

//// 기본값
const myDefaultValues = {
	spinning: true,
	speed: 1,
}

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

// 사용
export function main() {
	//// 엔티티 생성
	const wheel = engine.addEntity()
	const wheel2 = engine.addEntity()

	//// 기본값을 사용해 컴포넌트 초기화
	WheelSpinComponent.create(wheel)

	//// 다른 값은 기본값을 사용하고 하나의 사용자 정의 값만 지정해 컴포넌트 초기화
	WheelSpinComponent.create(wheel2, { speed: 5 })
}
```

위 예제는 `WheelSpinComponent` 컴포넌트를 정의하며, 사용할 schema와 기본값 집합을 모두 포함합니다. 그런 다음 값 없이 이 컴포넌트의 복사본을 초기화하면 기본값으로 설정된 값들이 사용됩니다.

## 변경 사항 구독

흔히 사용하는 사례는 특정 컴포넌트의 데이터가 변경된 경우에만 함수를 실행하는 것입니다. 다음을 사용하세요 [OnChange](/creator/content-creator-ko/sdk7/architecture/subscribe-to-changes.md) 함수는 시스템을 정의하거나 이전 값과 새 값을 명시적으로 비교할 필요를 없애 줍니다.

```ts
export function main() {
	// 엔티티 생성 등

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

## 컴포넌트를 사용하기 위한 시스템 만들기

컴포넌트를 정의하고 씬의 엔티티에 추가했다면, [시스템](/creator/content-creator-ko/sdk7/architecture/systems.md) 를 만들어 이 컴포넌트에 저장된 데이터를 활용한 로직을 수행할 수 있습니다.

```ts
// 컴포넌트 정의
export const WheelSpinComponent = engine.defineComponent('WheelSpinComponent', {
	spinning: Schemas.Boolean,
	speed: Schemas.Float,
})

// 사용
export function main() {
	// 엔티티 생성
	const wheel1 = engine.addEntity()
	const wheel2 = engine.addEntity()

	// 컴포넌트 인스턴스 생성
	WheelSpinComponent.create(wheel1, {
		spinning: true,
		speed: 10,
	})

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

// 이 엔티티들을 순회할 시스템 정의
export function spinSystem(dt: number) {
	// WheelSpinComponent가 있는 모든 엔티티를 순회
	for (const [entity, wheelSpin] of engine.getEntitiesWith(
		WheelSpinComponent
	)) {
		// spinning == true일 때만 수행
		if (wheelSpin.spinning) {
			// 수정 가능한 Transform 컴포넌트 가져오기
			const transform = Transform.getMutable(entity)

			// 회전 값을 그에 맞게 업데이트
			transform.rotation = Quaternion.multiply(
				transform.rotation,
				Quaternion.fromAngleAxis(dt * wheelSpin.speed, Vector3.Up())
			)
		}
	}
}

// 엔진에 시스템 추가
engine.addSystem(spinSystem)
```

위 예제는 사용자 정의 `wheelSpinComponent`를 포함하는 모든 엔티티를 순회하고, 게임 루프의 매 틱마다 그것들을 조금씩 회전시키는 시스템을 정의합니다. 이 회전량은 각 엔티티의 컴포넌트 인스턴스에 저장된 `속도` 값에 비례합니다. 이 예제는 [컴포넌트 쿼리](/creator/content-creator-ko/sdk7/architecture/querying-components.md) 를 사용해 관련 있는 엔티티만 가져옵니다.


---

# 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-ko/sdk7/architecture/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.
