Unity에서 BSP 알고리즘으로 랜덤 던전 맵 만들기

로그라이크 게임에서는 실행할 때마다 다른 던전을 자주 만납니다. 이런 맵은 규칙과 난수를 이용해 자동으로 생성합니다. 이번 글에서는 BSP 알고리즘으로 최소한의 랜덤 던전 프로토타입을 만들어봅니다.

핵심 요약
BSP로 전체 맵을 여러 공간으로 분할합니다.
각 최종 공간에 사각형 방을 하나 생성합니다.
방 중심을 L자 통로로 연결합니다.
Seed를 사용해 같은 던전을 다시 생성합니다.
Tilemap 없이 Gizmos로 알고리즘부터 검증합니다.

목표

이번 프로토타입의 목표

게임을 완성하는 것이 목적은 아닙니다. BSP가 실제로 방과 통로를 만드는 과정을 확인합니다.

생성 데이터를 Tilemap과 분리합니다. 이후 실제 던전으로 확장하기 쉬운 구조를 만듭니다.

결과물

1
BSP 공간 분할

40 × 30 영역을 재귀적으로 여러 공간으로 나눕니다.

2
랜덤 방 생성

각 Leaf 내부에 크기와 위치가 다른 방을 만듭니다.

3
L자 통로

BSP 트리 관계를 사용해 떨어진 방을 연결합니다.

4
Gizmos 시각화

별도 그래픽 리소스 없이 Scene 뷰에서 결과를 확인합니다.

결과물로 얻을 수 있는 기대

  • BSP가 공간을 나누는 과정을 직접 확인할 수 있습니다.
  • 방과 통로 데이터를 게임 그래픽과 분리할 수 있습니다.
  • 같은 Seed로 특정 던전을 다시 만들 수 있습니다.
  • 이후 Tilemap 던전으로 자연스럽게 확장할 수 있습니다.
  • 몬스터와 아이템 배치용 방 데이터를 그대로 활용할 수 있습니다.

상세 설명

BSP 알고리즘은 무엇인가?

BSP는 Binary Space Partitioning의 약자입니다. 하나의 공간을 둘로 나누는 작업을 반복합니다.

┌────────────────────────────┐
│ │
│ 전체 맵 │
│ │
└────────────────────────────┘

처음에는 전체 맵 하나만 존재합니다. 이 공간을 두 영역으로 나눕니다.

┌──────────────┬─────────────┐
│ │ │
│ 영역 A │ 영역 B │
│ │ │
└──────────────┴─────────────┘

나눈 영역을 다시 둘로 나눕니다. 일정 깊이에 도달하면 분할을 멈춥니다.

중요한 점

BSP 자체가 방을 만드는 것은 아닙니다. 먼저 방이 들어갈 후보 공간을 나눕니다.

실제 방의 크기와 위치는 각 Leaf 안에서 따로 계산합니다.

공간 분할
방 생성
통로 연결

프로토타입 구성

복잡한 구조는 사용하지 않습니다. 하나의 생성 스크립트에서 핵심 알고리즘을 확인합니다.

Assets
└─ BSPPrototype
├─ Scenes
│ └─ BspPrototype.unity
└─ Scripts
└─ BspMapGenerator.cs
맵 크기 40 × 30
최대 분할 깊이 4
최소 방 크기 4 × 4
방 여백 1
기본 Seed 12345
출력 방식 Gizmos

Scene 뷰에서는 색으로 생성 데이터를 구분합니다.

흰색 : 전체 맵 경계
파란색 : BSP 분할 영역
초록색 : 생성된 방
노란색 : 방을 연결하는 통로

1단계. 공간 나누기

1

분할 가능한 크기인지 검사합니다

방보다 BSP 영역이 먼저 충분한 크기를 가져야 합니다. 방 주변의 Padding도 확보해야 합니다.

int minimumLeafWidth =
minRoomWidth + roomPadding * 2;
 
int minimumLeafHeight =
minRoomHeight + roomPadding * 2;

최소 방 크기가 4이고 Padding이 1이라면 최소 Leaf 크기는 6입니다.

bool canSplitVertically =
node.Area.width >= minimumLeafWidth * 2;
 
bool canSplitHorizontally =
node.Area.height >= minimumLeafHeight * 2;

두 자식 모두 방을 가져야 합니다. 그래서 최소 Leaf 크기의 두 배가 필요합니다.

2

긴 방향을 우선해 나눕니다

가로가 길면 세로로 자릅니다. 세로가 길면 가로로 자릅니다.

이 기준은 지나치게 길쭉한 공간이 생기는 현상을 줄입니다.

왜 최소 크기를 먼저 검사할까?

너무 작은 Leaf를 만들면 방 크기의 랜덤 범위가 잘못될 수 있습니다.

먼저 분할 조건을 제한하면 이 문제를 구조적으로 막을 수 있습니다.

2단계. Leaf 안에 방 만들기

더 이상 나누지 않은 영역을 Leaf라고 부릅니다. 각 Leaf에 실제 방을 하나 만듭니다.

int maximumRoomWidth =
node.Area.width - roomPadding * 2;
 
int maximumRoomHeight =
node.Area.height - roomPadding * 2;

먼저 Padding을 제외한 최대 방 크기를 구합니다. 이후 최소값과 최대값 사이에서 방 크기를 선택합니다.

int width = random.Next(
minRoomWidth,
maximumRoomWidth + 1);
 
int height = random.Next(
minRoomHeight,
maximumRoomHeight + 1);

방 위치도 Leaf 내부에서 랜덤하게 선택합니다. 따라서 방은 BSP 경계를 벗어나지 않습니다.

RectInt로 저장하는 이유

방 데이터를 렌더링 방식과 분리할 수 있습니다. 나중에 Tilemap에서도 같은 좌표를 사용할 수 있습니다.

3단계. 방과 방 연결하기

방만 만들면 서로 떨어진 섬처럼 남습니다. 따라서 모든 방을 이동 가능한 구조로 연결해야 합니다.

각 BSP 부모는 두 자식에서 대표 방을 하나씩 가져옵니다. 두 방의 중심을 L자 통로로 연결합니다.

Vector2Int start = Center(firstRoom.Value);
Vector2Int end = Center(secondRoom.Value);
 
Vector2Int turn = horizontalFirst
? new Vector2Int(end.x, start.y)
: new Vector2Int(start.x, end.y);
Room A ─────────┐
└──── Room B

통로는 두 방식 중 하나를 무작위로 선택합니다.

  • 가로 이동 후 세로 이동
  • 세로 이동 후 가로 이동

이 작업을 BSP 부모마다 반복합니다. 최종적으로 모든 영역이 하나의 연결 구조를 가집니다.

Seed를 사용하는 이유

랜덤 던전은 실행할 때마다 다른 결과를 만드는 것이 자연스럽습니다. 개발 중에는 같은 맵을 다시 볼 필요도 있습니다.

random = new System.Random(seed);

같은 설정과 Seed를 사용하면 같은 던전 구조를 다시 얻을 수 있습니다.

  • 특정 던전에서 발생한 버그를 재현할 수 있습니다.
  • 다른 사용자와 동일한 맵을 공유할 수 있습니다.
  • 자동 테스트에서 랜덤 결과를 고정할 수 있습니다.
  • 특정 던전을 다시 플레이하도록 만들 수 있습니다.

Unity에서 실행하기

1

씬을 엽니다

Assets/BSPPrototype/Scenes/BspPrototype.unity를 엽니다.

2

Gizmos를 켭니다

Scene 뷰 오른쪽 위에서 Gizmos를 활성화합니다.

3

Generate를 실행합니다

Play를 누르면 맵을 생성합니다. 컴포넌트 메뉴의 Generate에서도 직접 실행할 수 있습니다.

4

Seed를 변경합니다

Inspector에서 Seed 값을 바꿉니다. 다시 생성하면 다른 던전 구조를 확인할 수 있습니다.

이번 단계에서 일부러 제외한 기능

  • Tilemap 바닥과 벽
  • 문과 장식 오브젝트
  • 플레이어 시작 위치
  • 몬스터와 아이템 배치
  • A* 경로 탐색
  • 방 종류와 특수 룸
  • 맵 저장과 불러오기

처음부터 기능을 많이 넣으면 BSP 자체를 검증하기 어렵습니다.

먼저 방과 통로 좌표를 검증합니다. 그 다음 Tilemap과 게임 시스템을 추가하는 편이 안전합니다.

다음 단계

BSP 데이터
Floor Tile
Wall Tile
게임 오브젝트

다음 단계에서는 RectInt 방 데이터를 Tilemap에 전달하면 됩니다.

방 영역에는 바닥 타일을 배치합니다. 통로 좌표에도 바닥 타일을 이어서 배치합니다.

마지막으로 바닥 주변을 검사해 벽을 생성하면 실제 던전 형태를 만들 수 있습니다.

마무리

이번 프로토타입의 핵심 흐름은 단순합니다.

공간을 나누고, 방을 만들고, 방 사이를 통로로 연결합니다.

Tilemap보다 논리 데이터를 먼저 검증하면 문제를 찾기 쉽습니다.

BSP 구조가 안정적으로 동작하면 실제 게임용 던전으로 확장할 수 있습니다.

 

전체 코드

BspMapGenerator.cs
0.01MB

Unity 6.5 · 2D Profiler
```
Sprite Atlas 최적화 전후를 2D Profiler로 직접 비교하기
Sprite Atlas를 만들었다고 최적화가 끝난 것은 아닙니다. 실제 프레임에서 Atlas를 사용하는지 확인해야 합니다.
목적: Sprite Atlas의 실제 사용 여부와 내부 사용 영역을 2D Profiler로 확인합니다.
```
2D Profiler에서 볼 항목
```
Sprite Count / Sprites Rendered
로드된 Sprite 수와 현재 프레임에서 사용한 Sprite 수를 확인합니다.
SpriteAtlas Count / SpriteAtlases Rendered
로드된 Atlas 수와 현재 프레임에서 사용한 Atlas 수를 확인합니다.
Usage
Atlas 영역 중 현재 프레임에서 실제 사용한 Sprite 영역 비율입니다.
Usage를 FPS 수치처럼 해석하면 안 됩니다.
Usage는 Atlas 내부 사용 영역을 보여줍니다. 패킹 효율이나 FPS 자체를 의미하지 않습니다.
```
씬별 목표와 차이
```
목표 Sprite Atlas 확인점
A NoAtlas Atlas 미적용 기준 25 / 25 0 / 0 개별 Texture 사용
B EfficientAtlas 24개 Sprite를 2개 Atlas로 구성 25 / 25 2 / 2 Gameplay 16개 + UI 8개
C WastefulAtlas 미사용 Sprite를 대량 포함 25 / 25 1 / 1 같은 화면에서도 Usage 감소
D FourSprites 4종 Sprite만 반복 배치 13 / 5 1 / 1 배치 수와 고유 Sprite 수 구분
```
비교에서 확인할 핵심
```
A → B
화면은 같습니다. Atlas Rendered는 0에서 2로 바뀝니다.
B → C
Rendered 수는 같습니다. 미사용 Sprite 때문에 Usage는 낮아질 수 있습니다.
D
오브젝트 수와 고유 Sprite 수는 다를 수 있습니다.
```
C와 D가 의미하는 것
```
C WastefulAtlas — Atlas 낭비 확인
같은 24개 Sprite를 표시합니다. 그러나 사용하지 않는 Sprite 80개도 Atlas에 넣습니다. 화면은 같아도 실제 사용 영역은 작습니다.
D FourSprites — 반복 배치 해석
SpriteRenderer가 많아도 같은 Sprite를 반복할 수 있습니다. Profiler 수치는 배치된 오브젝트 수와 다를 수 있습니다.
Usage의 의미
Usage는 선택한 프레임에서 실제 렌더링에 사용한 영역의 비율입니다.
낮은 Usage는 큰 Atlas 일부만 사용한다는 신호가 될 수 있습니다.
```
측정 방법
```
Window > Analysis > Profiler를 엽니다. 이후 2D 모듈을 선택합니다.
각 씬을 실행합니다. 상태가 안정된 프레임을 하나 선택합니다.
Count, Rendered, Atlas 상세 목록과 Usage를 함께 비교합니다.
Physics 2D와 혼동하지 않습니다. 이번 글에서 사용하는 것은 Unity 6.5의 2D Profiler 모듈입니다.
```
씬 A — NoAtlas
```
A
Sprite Atlas를 사용하지 않는 기준 상태
모든 Sprite를 개별 Texture로 사용합니다.
Sprite 25 / Rendered 25
Atlas는 사용하지 않습니다.
게임뷰
2D Profiler
```
씬 B — EfficientAtlas
```
B
Gameplay과 UI를 두 개 Atlas로 분리
화면에 함께 표시되는 Sprite를 목적별 Atlas로 묶습니다.
Gameplay Atlas와 UI Atlas를 각각 사용합니다.
Atlas Count / Rendered = 2 / 2
사용한 Sprite Atlas
게임뷰
2D Profiler
```
씬 C — WastefulAtlas
```
C
하나의 큰 Atlas에 많은 Sprite를 포함
화면은 B와 비슷하게 유지합니다. 사용하지 않는 Sprite도 같은 Atlas에 넣습니다.
Atlas Count / Rendered = 1 / 1
Rendered 수만 보면 문제를 발견하기 어렵습니다.
사용한 Atlas
게임뷰
2D Profiler
```
낮은 Usage 사례
```
Usage
Atlas를 사용해도 항상 효율적인 것은 아니다
이 사례에서는 Usage가 약 3.1%로 나타납니다.
Usage 3.1%
큰 Atlas를 로드했지만 실제로 사용하는 Sprite 영역은 매우 작습니다.
사용한 Atlas와 실행 화면
2D Profiler
```
Profiler 수치를 해석할 때 주의할 점
```
Usage가 낮다고 무조건 잘못된 Atlas는 아닙니다. 로딩 방식과 메모리 사용량도 함께 확인해야 합니다.
SpriteRenderer 개수와 Sprites Rendered는 같은 의미가 아닙니다. 같은 Sprite를 여러 번 반복해서 사용할 수 있습니다.
Atlas Count가 적다고 항상 더 좋은 것도 아닙니다. 함께 사용하는 Sprite를 어떤 기준으로 묶었는지가 더 중요합니다.
```
정리
```
2D Profiler는 Sprite Atlas가 실제 프레임에서 사용되는지 보여줍니다.
Count와 Rendered만 보면 Atlas 낭비를 놓칠 수 있습니다.
Usage까지 함께 보면 Atlas 구성의 효율을 더 쉽게 판단할 수 있습니다.
```

Unity에서 Play 버튼을 누르면 게임이 바로 실행되는 것처럼 보입니다.

하지만 내부에서는 여러 초기화 작업이 실행됩니다.

그중 하나가 Domain Reload입니다.

Domain Reload는 static 변수와 이벤트 상태를 초기화하는 역할을 합니다.

프로젝트가 커지면 이 과정도 시간이 걸릴 수 있습니다.

그래서 Unity에서는 Domain Reload를 끌 수 있습니다.

문제는 끈 순간부터 static 상태를 직접 관리해야 한다는 점입니다.


핵심 요약

  • Domain Reload는 static 상태를 초기화합니다.
  • 끄면 Play Mode 진입 시간이 줄어들 수 있습니다.
  • 대신 static 값이 이전 Play 상태를 유지할 수 있습니다.
  • SubsystemRegistration으로 static 값을 직접 초기화할 수 있습니다.
  • static 이벤트도 중복 등록을 주의해야 합니다.

목표

Domain Reload를 껐을 때 static 값이 왜 남는지 확인합니다.

그리고 안전하게 초기화하는 방법을 알아봅니다.


결과물

최종적으로 다음 패턴을 사용합니다.

[RuntimeInitializeOnLoadMethod(
    RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStaticData()
{
    playCount = 0;
}

Domain Reload를 꺼도 Play Mode마다 static 값을 초기화할 수 있습니다.


결과물로 얻을 수 있는 기대

Play Mode 진입 시간을 줄이면서 static 상태를 안정적으로 관리할 수 있습니다.

Domain Reload를 껐을 때 발생하는 이상한 Editor 버그도 줄일 수 있습니다.


1. 어떻게 끄고 켜지?

Enter Play Mode Settings 옵션

Unity 6에서는 Project Settings > Editor > Enter Play Mode Settings에서 동작을 선택할 수 있습니다.

이미지처럼 4가지 옵션이 있습니다.

옵션의미
Reload Domain and Scene Domain과 Scene을 모두 다시 로드합니다. 가장 일반적인 초기화 방식입니다.
Reload Scene only Scene만 다시 로드합니다. static 상태는 유지될 수 있습니다.
Reload Domain only Domain만 다시 로드합니다. Scene 상태는 유지합니다.
Do not reload Domain or Scene 둘 다 다시 로드하지 않습니다. Play Mode 진입은 빠르지만 직접 상태를 관리해야 합니다.

2. Domain Reload란?

Domain Reload는 Unity가 스크립팅 상태를 다시 초기화하는 과정입니다.

기본적인 Play Mode에서는 static 변수도 초기화합니다.

예를 들어 다음 코드가 있다고 가정하겠습니다.

public static int PlayCount = 0;

게임에서 값을 변경합니다.

PlayCount = 10;

Play Mode를 종료하고 다시 실행합니다.

Domain Reload가 동작하면 다시 초기값으로 돌아갑니다.

첫 번째 Play
PlayCount = 0

값 변경
PlayCount = 10

Stop

두 번째 Play
PlayCount = 0

개발자가 별도 초기화 코드를 작성하지 않아도 됩니다.


3. Domain Reload를 끄면 어떻게 될까?

Domain Reload를 끄면 Play Mode 진입 시간을 줄일 수 있습니다.

하지만 static 값이 자동으로 초기화되지 않습니다.

간단한 테스트 코드를 만들어 보겠습니다.

using UnityEngine;

public class DomainReloadTest : MonoBehaviour
{
    private static int playCount;

    private void Start()
    {
        playCount++;

        Debug.Log($"Play Count : {playCount}");
    }
}

Domain Reload가 동작하면 매번 다음과 같이 출력됩니다.

Play Count : 1

하지만 Domain Reload를 끄면 값이 누적될 수 있습니다.

첫 번째 Play
Play Count : 1

두 번째 Play
Play Count : 2

세 번째 Play
Play Count : 3

playCount가 이전 값을 유지하기 때문입니다.

버그가 아니라 정상적인 동작입니다.


4. static 값을 직접 초기화하기

이 문제는 RuntimeInitializeOnLoadMethod로 해결할 수 있습니다.

using UnityEngine;

public class DomainReloadTest : MonoBehaviour
{
    private static int playCount;

    [RuntimeInitializeOnLoadMethod(
        RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void ResetStaticData()
    {
        playCount = 0;
    }

    private void Start()
    {
        playCount++;

        Debug.Log($"Play Count : {playCount}");
    }
}

이제 Domain Reload를 꺼도 결과는 같습니다.

첫 번째 Play
Play Count : 1

두 번째 Play
Play Count : 1

세 번째 Play
Play Count : 1

매번 Runtime 시작 시 직접 초기화하기 때문입니다.


5. 왜 SubsystemRegistration을 사용할까?

RuntimeInitializeOnLoadMethod에는 실행 시점을 지정할 수 있습니다.

이번에는 다음 값을 사용했습니다.

RuntimeInitializeLoadType.SubsystemRegistration

이 시점은 Runtime 초기 과정에서 실행됩니다.

Scene의 Awake()보다 먼저 static 상태를 초기화하기에 적합합니다.

따라서 Domain Reload를 끈 프로젝트에서 자주 사용할 수 있습니다.


6. static 이벤트도 주의해야 한다

Domain Reload를 끄면 변수만 확인하면 안 됩니다.

static 이벤트 등록 상태도 남을 수 있습니다.

예를 들어 다음 코드가 있습니다.

Application.quitting += OnQuit;

Play Mode에 들어갈 때마다 등록하면 중복될 수 있습니다.

그러면 하나의 이벤트가 여러 번 호출됩니다.

안전하게 처리하려면 기존 등록을 먼저 제거합니다.

[RuntimeInitializeOnLoadMethod(
    RuntimeInitializeLoadType.SubsystemRegistration)]
static void Initialize()
{
    Application.quitting -= OnQuit;
    Application.quitting += OnQuit;
}

핵심은 이 패턴입니다.

이벤트 -= 함수;
이벤트 += 함수;

기존 등록을 제거한 뒤 다시 등록합니다.


7. Domain Reload를 꺼도 될까?

꺼도 됩니다.

다만 다음 두 항목을 직접 관리해야 합니다.

확인 항목이유

static 변수 이전 Play 값이 남을 수 있음
static 이벤트 이벤트가 중복 등록될 수 있음

프로젝트가 작다면 Domain Reload를 그대로 사용해도 됩니다.

Play Mode 진입 시간이 부담스러워지면 비활성화를 검토하면 됩니다.


마무리

Domain Reload를 끄는 목적은 간단합니다.

Play Mode 진입 시간을 줄이는 것입니다.

하지만 Unity가 해주던 static 초기화도 함께 사라집니다.

따라서 static 상태를 직접 관리해야 합니다.

가장 먼저 기억할 코드는 다음입니다.

[RuntimeInitializeOnLoadMethod(
    RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStaticData()
{
    // static 데이터 초기화
}

static 이벤트도 함께 확인해야 합니다.

Domain Reload를 끄기 전에 이 두 가지만 점검해도 많은 문제를 예방할 수 있습니다.

유니티 : https://github.com/Unity-Technologies/2d-renderer-samples?utm_source=chatgpt.com

최신화가 안되어서. 최신화 할 겸 만들었습니다.

수정한 샘플 : https://github.com/MinByungGil/2d-renderer-samples

 

UNITY 2D RENDERER

씬으로 배우는 Unity 2D 조명과 화면 표현

Unity의 2D 조명은 빛 하나만 배치해서 완성하지 않습니다. 빛의 종류, 대상 레이어, 표면 방향, 재질, 후처리를 함께 설계합니다.

Unity 6 URP 2D Renderer 10개 샘플 씬 주니어 개발자용
이 글은 기술 용어를 화면 변화와 연결해 설명합니다. 먼저 결과를 보고, 다음에 Inspector 설정을 확인하세요.

핵심 요약

01

한 장면, 한 가지 질문

각 씬은 하나의 렌더링 개념을 집중해서 보여 줍니다.

02

빛을 기능으로 확장

기본 조명을 Normal Map, Mask, Shadow, 후처리와 연결합니다.

03

한 번에 하나씩 비교

설정 하나만 바꾸고 화면 변화의 원인을 확인합니다.

이 글의 목표

배우는 내용

2D Light가 화면에 영향을 주는 과정을 이해합니다. 각 씬의 목적과 관찰 포인트도 함께 확인합니다.

얻는 결과

필요한 샘플 씬을 직접 고릅니다. 조명, 그림자, 재질, 카메라 설정을 비교하며 문제 원인을 찾습니다.

목차

프로젝트 결과물

이 프로젝트는 실행 가능한 씬 10개와 공유 기반 폴더 1개를 제공합니다. 각 씬은 독립적으로 열어 확인합니다.

 

Lights

빛의 종류, 표면 반응, 적용 범위를 비교합니다. 3개 씬을 제공합니다.

Shadows

하나의 그림자와 여러 그림자의 합성을 비교합니다. 2개 씬을 제공합니다.

Post Processing

Volume과 Bloom으로 최종 화면 분위기를 바꿉니다. 1개 씬을 제공합니다.

Shader Graph

재질 노드로 스프라이트 색상 흐름을 바꿉니다. 1개 씬을 제공합니다.

Pixel Perfect Camera

스프라이트, Tilemap, UI의 픽셀 표현을 비교합니다. 3개 씬을 제공합니다.

Common

URP Asset, 2D Renderer Data, 스프라이트와 Volume 설정을 공유합니다.

추천 학습 순서

  1. Common에서 공유 설정과 에셋 위치를 확인합니다.
  2. Lights에서 빛의 종류와 표면 반응을 살펴봅니다.
  3. Shadows에서 빛과 그림자의 관계를 비교합니다.
  4. Post Processing에서 화면 효과를 추가합니다.
  5. Shader Graph에서 재질 표현을 바꿉니다.
  6. Pixel Perfect Camera에서 최종 화면과 UI를 확인합니다.
2D 조명을 읽는 다섯 가지 질문

Inspector에서 아래 순서로 질문하면 빛 문제를 빠르게 좁힐 수 있습니다.

어떤 Light인가?
어디에 비추는가?
표면 방향이 있는가?
어떻게 합치는가?
최종 효과를 더하는가?

폴더별 씬 상세 설명

Common/

Common/은 실행 씬을 모아 둔 폴더가 아닙니다. 다른 씬이 함께 사용하는 렌더링 설정과 시각 에셋을 보관합니다.

Render Pipeline

URP Asset과 2D Renderer Data를 보관합니다.

Sprites

스프라이트, Normal Map, 외곽선, 쿠키를 보관합니다.

Volume Profile

여러 씬이 참고하는 화면 효과 설정을 보관합니다.

공유 원칙

공유 설정이 여러 씬의 기본 렌더링 흐름을 결정합니다.

Lights/

이 폴더는 2D Light를 세 단계로 설명합니다. 빛의 모양, 표면 반응, 조명 대상을 차례로 살펴봅니다.

01

1 Light Types

Assets/Samples/2D Renderer/Lights/1 Light Types.unity

요약

다섯 종류의 2D Light가 빛의 모양과 범위를 바꾸는 방식을 비교합니다.

목적

Global, Parametric, Freeform, Point, Sprite Light 2D를 한 화면에서 살펴봅니다.

결과물

배경과 스프라이트에 다섯 종류의 조명을 배치한 비교 씬입니다.

사용하면 할 수 있는 것

장면 목적에 맞는 Light 종류를 고르고 위치, 색, 강도를 조절합니다.

상세 설명

Global Light 2D는 넓은 영역에 기본 밝기를 만듭니다. Parametric Light 2D는 정해진 도형으로 빛을 만듭니다.

Freeform Light 2D는 점을 움직여 빛의 모양을 직접 만듭니다. Point Light 2D는 중심에서 사방으로 빛을 퍼뜨립니다.

Sprite Light 2D는 이미지 형태를 빛의 무늬로 사용합니다. 같은 스프라이트도 빛의 위치와 범위에 따라 다르게 보입니다.

추천 실험
  1. 각 Light를 하나씩 끄며 역할을 확인합니다.
  2. Light 위치를 바꾸며 조명 영역을 비교합니다.
  3. 색과 강도를 차례로 바꾸며 화면 변화를 기록합니다.
02

2 Normal Map

Assets/Samples/2D Renderer/Lights/2 Normal Map.unity

요약

Normal Map이 표면 방향을 알려 주고 조명이 명암을 계산합니다.

목적

Normal Map을 연결한 스프라이트와 일반 스프라이트의 차이를 비교합니다.

결과물

GlowRocks B와 배경에 Freeform Light를 비추는 비교 씬입니다.

사용하면 할 수 있는 것

2D 그림에 표면 명암과 입체감을 추가하는 방법을 이해합니다.

상세 설명

Normal Map은 표면이 어느 방향을 향하는지 색으로 저장합니다. 2D Light는 그 방향 정보를 사용해 밝기와 명암을 계산합니다.

빛을 움직이면 표면의 밝은 부분과 어두운 부분도 움직입니다. Normal Map을 끄면 표면 반응이 단순해집니다.

관련 텍스처는 Common/Sprites/GlowRocks B_n.png입니다. 같은 Light를 Normal Map 사용 전후에 비춰 결과를 비교하세요.

03

3 Masks

Assets/Samples/2D Renderer/Lights/3 Masks.unity

요약

Light Mask는 빛의 대상을 고릅니다. Blend Style은 빛의 합성 방식을 고릅니다.

목적

배경과 오브젝트에 서로 다른 조명을 적용하는 방법을 비교합니다.

결과물

Global Light 2D, Key Light, Rim Light를 한 씬에 배치합니다.

사용하면 할 수 있는 것

Sorting Layer별 조명과 주 조명, 외곽 조명을 설계합니다.

상세 설명

Global Light 2DBackground 레이어를 비춥니다. Key LightRim LightSpace Junk 레이어를 비춥니다.

Light Inspector의 Apply To Sorting Layers가 조명 대상을 고릅니다. 오브젝트가 빛 영역 안에 있어도 레이어가 다르면 빛을 받지 않습니다.

Key Light는 기본 합성을 사용합니다. Rim Light는 두 번째 Rim Blend Style을 사용해 오브젝트 외곽을 강조합니다.

Rim Light는 외곽선을 자동으로 그리지 않습니다. 대상 레이어, 조명 영역, 보조 텍스처를 모두 확인해야 합니다.

추천 실험
  1. Global, Key, Rim Light를 차례로 끕니다.
  2. Key Light의 대상 Sorting Layer를 해제합니다.
  3. Rim Light의 Blend Style을 바꾸고 결과를 비교합니다.

Shadows/

이 폴더는 빛이 만드는 어두운 영역을 설명합니다. 하나의 그림자부터 여러 그림자의 합성까지 비교합니다.

04

1 Shadows

Assets/Samples/2D Renderer/Shadows/1 Shadows.unity

요약

하나의 2D Light와 Shadow Caster가 만드는 기본 그림자를 살펴봅니다.

목적

빛과 오브젝트의 위치가 그림자 방향과 길이에 미치는 영향을 확인합니다.

결과물

Rock과 Parametric Light가 만드는 단일 그림자 씬입니다.

사용하면 할 수 있는 것

Shadow Caster를 연결하고 그림자 경계를 비교합니다.

상세 설명

Shadow Caster는 스프라이트가 그림자를 만드는 경계를 정의합니다. 빛과 Rock의 상대 위치가 그림자 방향을 결정합니다.

빛과 Rock 사이의 거리는 그림자 길이에 영향을 줍니다. Light와 Rock을 옮기며 그림자 변화를 관찰하세요.

05

2 Shadow Composite

Assets/Samples/2D Renderer/Shadows/2 Shadow Composite.unity

요약

여러 Rock 오브젝트가 만든 그림자를 하나의 빛 아래에서 합칩니다.

목적

그림자 영역이 겹칠 때 최종 화면이 달라지는 과정을 확인합니다.

결과물

여러 Rock과 Shadow Caster를 배치한 합성 그림자 씬입니다.

사용하면 할 수 있는 것

오브젝트 배치로 그림자 모양을 바꾸고 각 기여도를 확인합니다.

상세 설명

여러 Rock의 그림자 영역이 겹치면 최종 그림자 모양도 달라집니다. Rock과 Light의 위치를 한 번에 하나씩 바꾸세요.

개별 Shadow Caster를 끄면 해당 Rock의 기여를 확인할 수 있습니다. 이 씬은 그림자를 조명 결과의 조각으로 이해하도록 돕습니다.

Post Processing/

이 폴더는 조명 결과에 화면 효과를 추가하는 방법을 설명합니다.

06

1 Post Processing

Assets/Samples/2D Renderer/Post Processing/1 Post Processing.unity

요약

Volume이 2D 조명 장면에 후처리 효과를 추가합니다.

목적

Bloom이 밝은 영역과 전체 화면 분위기를 바꾸는 과정을 확인합니다.

결과물

GlowRocks B와 조명 영역에 Bloom Profile을 적용한 씬입니다.

사용하면 할 수 있는 것

조명과 후처리의 역할을 나누고 화면 분위기를 설계합니다.

상세 설명

2D Light는 장면 안의 빛과 색을 만듭니다. Post Processing은 완성된 화면에 추가 효과를 계산합니다.

Profiles/Bloom Profile.asset은 Bloom 설정을 담습니다. Bloom은 밝은 영역 주변에 부드러운 빛 번짐을 만듭니다.

먼저 Volume 효과를 끕니다. 다음에 Bloom을 켜고 같은 장면을 비교합니다. 마지막으로 효과 강도를 조절합니다.

Shader Graph/

이 폴더는 재질이 화면 색을 바꾸는 과정을 설명합니다.

07

1 Shader Graph

Assets/Samples/2D Renderer/Shader Graph/1 Shader Graph.unity

요약

Shader Graph 재질이 2D Lit Sprite의 색상 표현을 바꿉니다.

목적

기본 재질과 색상 반전 재질의 차이를 비교합니다.

결과물

RockInvert Color 재질을 연결한 비교 씬입니다.

사용하면 할 수 있는 것

노드를 연결해 재질 색상 흐름을 만들고 조명과 함께 비교합니다.

상세 설명

Shader Graph는 코드를 직접 작성하지 않고 재질 흐름을 구성합니다. Invert Color 그래프는 입력 색상의 반대 색상을 출력합니다.

2D Lit Sprite는 재질과 조명 결과를 함께 화면에 그립니다. 따라서 같은 조명도 재질에 따라 다른 색을 보여 줍니다.

Materials/Invert Color.matShader Graphs/Invert Color.shadergraph를 차례로 확인하세요.

Pixel Perfect Camera/

이 폴더는 화면을 픽셀 격자에 맞추는 방법을 비교합니다. 조명보다 카메라, 스프라이트, 타일, UI 표현을 다룹니다.

08

1 SpriteRenderer Examples

Assets/Samples/2D Renderer/Pixel Perfect Camera/1 SpriteRenderer/1 SpriteRenderer Examples.unity

요약

SpriteRenderer 이동 사례를 두 카메라로 비교합니다.

목적

오브젝트 이동 방식이 픽셀 정합과 화면 가장자리에 미치는 영향을 확인합니다.

결과물

정지, 부모 이동, 애니메이션, 물리, 스크립트 이동 사례를 배치합니다.

사용하면 할 수 있는 것

일반 카메라와 Pixel Perfect Camera의 차이를 설명합니다.

상세 설명

장면은 Stationary, Animated, Physically Controlled 같은 사례를 제공합니다. 각 사례는 서로 다른 이동 방식을 보여 줍니다.

화면의 Pixel Perfect 토글로 두 카메라를 전환합니다. 정지 오브젝트와 이동 오브젝트의 가장자리를 차례로 비교하세요.

09

2 Tilemap Examples

Assets/Samples/2D Renderer/Pixel Perfect Camera/2 Tilemap/2 Tilemap Examples.unity

요약

Dungeon과 애니메이션 Water Tilemap을 두 카메라로 비교합니다.

목적

타일 경계와 카메라 이동이 픽셀 정합에 미치는 영향을 확인합니다.

결과물

던전, 물 타일, 로봇 캐릭터를 배치한 이동형 비교 씬입니다.

사용하면 할 수 있는 것

타일 경계, 카메라 추적, Rule Tile 결과를 함께 관찰합니다.

상세 설명

Dungeon은 Rule Tile로 만든 던전 타일을 보여 줍니다. Water는 애니메이션 타일로 만든 물 표현을 보여 줍니다.

실행 중 방향키로 로봇 캐릭터를 움직입니다. Pixel Perfect 토글로 카메라를 바꾸고 타일 경계를 비교하세요.

10

3 UI Scaling Example

Assets/Samples/2D Renderer/Pixel Perfect Camera/3 UI Scaling Example/3 UI Scaling Example.unity

요약

기준 해상도에 맞춰 카메라와 UI 크기를 비교합니다.

목적

화면 크기와 종횡비가 UI 배치에 미치는 영향을 확인합니다.

결과물

기준 해상도 320 x 180과 Screen Space - Camera UI를 사용하는 씬입니다.

사용하면 할 수 있는 것

UI 크기, Crop Frame, Custom Canvas Scaler의 역할을 비교합니다.

상세 설명

Game 뷰의 해상도와 종횡비를 바꾸며 UI를 비교합니다. Canvas의 기존 Canvas Scaler를 끄고 Custom Canvas Scaler를 확인합니다.

Pixel Perfect Camera의 Crop Frame X/Y 설정도 함께 살펴봅니다. Screen Space - Camera 조건에서 UI 배치와 잘림을 비교하세요.

장면을 확인하는 실험 규칙

기본 화면을 먼저 확인합니다.
관찰할 오브젝트와 Light를 고릅니다.
설정 하나만 바꿉니다.
변경 전후 화면을 비교합니다.
바꾼 값과 결과를 기록합니다.
같은 해상도와 종횡비를 유지합니다.

빛이 보이지 않을 때 확인할 순서

  1. 스프라이트의 Sorting Layer를 확인합니다.
  2. Light의 Apply To Sorting Layers를 확인합니다.
  3. Light가 오브젝트를 실제로 덮는지 확인합니다.
  4. Light의 Intensity가 0에 가깝지 않은지 확인합니다.
  5. 스프라이트가 2D Renderer용 재질과 셰이더를 사용하는지 확인합니다.
검증 안내: 이 글은 저장소 문서와 현재 에셋 구조를 기준으로 작성했습니다. Unity 컴파일과 10개 씬의 최종 Play Mode 검증은 별도로 확인해야 합니다.

용어 사전

2D Light2D 그림과 배경을 밝히는 조명 요소입니다.
Normal Map표면 방향을 저장해 명암 계산을 돕는 이미지입니다.
Light Mask빛이 영향을 줄 Sorting Layer를 고르는 필터입니다.
Sorting Layer스프라이트를 화면에 그리는 그룹 순서입니다.
Blend Style여러 빛을 최종 색에 섞는 방식입니다.
Shadow Caster오브젝트가 그림자를 만들 영역을 정의합니다.
Volume화면 효과 설정을 모아 관리하는 영역입니다.
Bloom밝은 부분 주변에 빛 번짐을 추가하는 효과입니다.
Shader Graph노드를 연결해 재질 표현을 만드는 도구입니다.
Pixel Perfect Camera픽셀 단위 표현을 유지하도록 화면을 계산하는 카메라입니다.
Unity 6.6 Dictionary 직렬화 쉽게 이해하기
💡 한 줄 요약
Unity 6.6은 Dictionary<TKey, TValue>를 직접 저장합니다. Inspector에서도 Key와 Value를 바로 편집할 수 있습니다.
핵심 요약
[SerializeField]를 붙이면 Dictionary를 Unity가 직렬화합니다.
Inspector에서 Key와 Value를 직접 수정할 수 있습니다.
List 두 개로 Dictionary를 흉내 낼 필요가 줄었습니다.
필드 타입은 Dictionary<TKey, TValue>를 사용합니다.
public Dictionary도 [SerializeField]를 붙입니다.
목표
Unity 6.6에서 Dictionary를 Inspector 데이터로 사용하는 방법을 알아봅니다.
지원 범위와 주의사항도 함께 확인합니다.
결과물
using System.Collections.Generic;
using UnityEngine;
 
public class Inventory : MonoBehaviour
{
[SerializeField]
private Dictionary<string, int> itemCounts = new();
}
Inspector에서는 Key와 Value를 표 형태로 편집합니다.
Key
Value
Potion
5
Sword
1
결과물로 얻을 수 있는 기대
Key 기반 데이터를 Inspector에서 바로 관리할 수 있습니다.
ScriptableObject 데이터 작성도 더 단순해집니다.
별도 SerializableDictionary와 변환 코드도 줄일 수 있습니다.
상세 설명
1. 무엇이 달라졌나
기존 Unity는 일반적인 Dictionary를 기본 직렬화하지 못했습니다.
개발자는 List 두 개나 커스텀 직렬화 코드를 자주 사용했습니다.
Unity 6.6은 Dictionary<TKey, TValue>를 직접 직렬화합니다.
Inspector도 Dictionary 전용 편집 기능을 제공합니다.
2. Dictionary에는 [SerializeField]를 붙인다
[SerializeField]
private Dictionary<string, int> itemCounts = new();
Unity가 Dictionary를 저장하도록 필드에 속성을 지정합니다.
3. Inspector 표시 순서와 실제 순서는 다르다
Inspector는 Key 기준으로 정렬해서 보여줄 수 있습니다.
하지만 화면 순서와 실제 직렬화 순서는 다를 수 있습니다.
실제 데이터는 항목을 추가한 순서를 유지합니다.
4. 사용할 수 있는 Key와 Value
int, float, bool, string
enum
Vector2, Vector3, Color
[Serializable] 클래스와 struct
UnityEngine.Object 파생 타입
기본형부터 Unity 타입과 직렬화 가능한 커스텀 타입까지 사용할 수 있습니다.
5. Value에는 List와 배열도 사용할 수 있다
[SerializeField]
private Dictionary<int, List<int>> stageRewards = new();
반대로 Key에는 List나 배열을 사용할 수 없습니다.
// 지원하지 않음
Dictionary<List<int>, int>
6. List나 배열 안에 Dictionary를 직접 넣지 않는다
List<Dictionary<string, int>>
Dictionary<string, int>[]
필요하면 Dictionary를 직렬화 가능한 타입으로 감쌉니다.
[System.Serializable]
public class ItemTable
{
[SerializeField]
public Dictionary<string, int> items = new();
}
 
[SerializeField]
private List<ItemTable> tables = new();
7. 커스텀 타입을 Key로 쓸 때 주의한다
커스텀 class는 기본적으로 객체 참조를 비교합니다.
필드 값이 같아도 다른 객체라면 다른 Key로 판단할 수 있습니다.
EqualsGetHashCode 구현을 권장합니다.
값 타입은 IEquatable<T> 구현도 유용합니다.
8. 중복 Key는 Inspector에서 확인한다
Inspector에는 중복 Key가 잠시 표시될 수 있습니다.
런타임 Dictionary는 동일한 Key를 두 개 저장하지 못합니다.
9. null Key도 주의한다
null Key는 런타임 Dictionary에 정상 항목으로 들어가지 않습니다.
Inspector에서 비어 있는 Key를 발견하면 먼저 수정하세요.
10. Inspector 표시를 꾸밀 수 있다
[SerializeField]
[DictionaryDisplay(
keyLabel = "Item",
valueLabel = "Drop Chance",
keyColumnFraction = 0.6f)]
private Dictionary<string, float> drops = new();
열 이름과 Key 영역 비율을 직접 지정할 수 있습니다.
11. Prefab에서는 순서 변경을 조심한다
Prefab Instance는 Dictionary 항목별 Override를 지원합니다.
Unity는 내부 직렬화 위치를 기준으로 Override를 기록합니다.
항목을 추가하거나 삭제하면 Override 위치가 달라질 수 있습니다.
Prefab Dictionary를 수정한 뒤 Instance Override를 다시 확인하세요.
12. 실전에서 어디에 쓰면 좋은가
아이템 ID → 보유 개수
몬스터 ID → 능력치
스테이지 번호 → 보상 목록
사운드 이름 → AudioClip
캐릭터 타입 → Prefab
설정 이름 → ScriptableObject
[SerializeField]
private Dictionary<string, AudioClip> sounds = new();
 
public AudioClip GetSound(string id)
{
return sounds.TryGetValue(id, out var clip) ? clip : null;
}
DictionaryDisplay로 Inspector 표시 꾸미기
같은 Dictionary 타입에 공통 표시 규칙도 지정할 수 있습니다.
using System.Collections.Generic;
using UnityEngine;
 
[assembly: DictionaryDisplayForType(
typeof(Dictionary<string, SkillLevel>),
layout = DictionaryLayout.OneColumnWithValueVisible,
keyLabel = "Skill",
valueLabel = "Level")]
 
[System.Serializable]
public struct SkillLevel
{
public int rank;
public float bonus;
}
 
public class Character : MonoBehaviour
{
[SerializeField]
private Dictionary<int, Dictionary<string, SkillLevel>> skillsPerTier = new();
}
 
public class AbilityBook : MonoBehaviour
{
[SerializeField]
private Dictionary<string, SkillLevel> learnedSkills = new();
}
주의사항
Dictionary마다 [SerializeField]가 필요합니다.
[SerializeReference]를 Dictionary 필드에 직접 붙일 수 없습니다.
Inspector 다중 객체 편집은 지원하지 않습니다.
Inspector 검색과 필터 기능은 없습니다.
Prefab Override와 순서 변경을 함께 사용할 때 주의합니다.
결론
Unity 6.6의 Dictionary 직렬화는 데이터 작성 방식을 크게 단순화합니다.
단순한 Key-Value 데이터라면 별도 SerializableDictionary부터 만들 필요가 없습니다.
먼저 [SerializeField] Dictionary<TKey, TValue>를 검토하면 됩니다.

Unity MCP 기능 요약

Unity 개발 작업에서의 일반적인 사용 빈도를 기준으로 활용도를 평가했습니다.

Assets

MCP 이름 기능 설명 활용도
AssetGeneration_GenerateAsset 이미지·텍스처 등의 에셋 생성 ★★★★☆
AssetGeneration_GetModels 에셋 생성에 사용할 AI 모델 조회 ★★☆☆☆
AssetGeneration_ConvertSprite 이미지를 Unity Sprite로 변환 ★★★★☆
AssetGeneration_ConvertToMaterial 생성 결과물을 Material로 변환 ★★★☆☆
AssetGeneration_ConvertToTexture 생성 결과물을 Texture로 변환 ★★★★☆
AssetGeneration_CreateAnimation 이미지나 에셋을 이용해 애니메이션 생성 ★★★☆☆
AssetGeneration_EditAnimation 생성된 애니메이션 수정 ★★★☆☆
AssetGeneration_GetComposition 에셋 생성 작업의 구성·결과 정보 조회 ★★☆☆☆
AssetGeneration_ManageInterrupted 중단된 에셋 생성 작업 확인·재개 ★★☆☆☆
AudioClip_Edit AudioClip 자르기 등 오디오 편집 ★★★☆☆
FindProjectAssets 프로젝트에서 조건에 맞는 에셋 검색 ★★★★★
ManageShader Shader 생성·수정·조회 ★★★★☆

Core

MCP 이름 기능 설명 활용도
RunCommand Unity Editor 명령이나 등록된 작업 실행 ★★★★★
ApplyTextEdits 코드·텍스트 파일에 부분 수정 적용 ★★★★★
CreateScript 새로운 C# 스크립트 생성 ★★★★★
DeleteScript 기존 스크립트 삭제 ★★☆☆☆
FindInFile 프로젝트 파일 내부 문자열 검색 ★★★★★
GetSha 파일 또는 프로젝트 상태 비교용 해시 조회 ★☆☆☆☆
ImportExternalModel 외부 3D 모델을 Unity 프로젝트로 가져오기 ★★★☆☆
ListResources 접근할 수 있는 Unity 리소스 목록 조회 ★★★★☆
ManageAsset 에셋 생성·이동·이름 변경·삭제·속성 관리 ★★★★★
ManageEditor 플레이·정지·일시정지 등 Editor 제어 ★★★★★
ManageGameObject GameObject와 컴포넌트 생성·수정·삭제 ★★★★★
ManageMenuItem Unity 메뉴 항목 조회·실행 ★★★☆☆
ManageScene Scene 생성·열기·저장 및 구조 관리 ★★★★★
ManageScript 스크립트 생성·조회·수정 통합 관리 ★★★★★
ManageScript_capabilities 지원되는 스크립트 작업 기능 조회 ★★☆☆☆
ReadResource Unity 프로젝트 리소스 내용 읽기 ★★★★☆
ScriptApplyEdits C# 스크립트에 구조화된 코드 수정 적용 ★★★★★
ValidateScript C# 스크립트 문법·컴파일 상태 검사 ★★★★★

Debug & Diagnostics

MCP 이름 기능 설명 활용도
GetConsoleLogs Unity Console의 로그·경고·오류 조회 ★★★★★
Profiler_GetBottomUpSampleTree 하위 호출부터 병목 원인을 역추적 ★★★☆☆
Profiler_GetCounterSummary CPU·메모리 등 선택한 카운터 요약 ★★★★☆
Profiler_GetCounterTable 프레임별 프로파일러 카운터 조회 ★★★☆☆
Profiler_GetFrameGcAllocations 특정 프레임의 GC 할당 원인 분석 ★★★★★
Profiler_GetFrameRangeGcAllocations 여러 프레임의 GC 할당 변화 분석 ★★★★★
Profiler_GetFrameRangeTopTimeSummary 지정 구간에서 시간이 많이 든 작업 요약 ★★★★★
Profiler_GetFrameSelfTimes 특정 프레임의 함수별 Self Time 조회 ★★★★☆
Profiler_GetFrameTopTimeSamples 특정 프레임의 주요 병목 샘플 조회 ★★★★★
Profiler_GetOverallGcAllocations 전체 프로파일링 구간의 GC 할당 요약 ★★★★☆
Profiler_GetRelatedSamplesTree 선택한 샘플과 연결된 호출 관계 조회 ★★★☆☆
Profiler_GetSampleGcAllocations 특정 샘플의 GC 할당량 조회 ★★★★☆
Profiler_GetSampleGcAllocationSummary 특정 샘플의 GC 할당 결과 요약 ★★★★☆
Profiler_GetSampleTimeSummary 선택한 함수·샘플의 실행시간 요약 ★★★★★
Profiler_GetSampleTimeSummary (variant) 조건 또는 범위를 지정한 실행시간 요약 ★★★★☆
ReadConsole Unity Console의 현재 출력 내용 읽기 ★★★★★

Editor

MCP 이름 기능 설명 활용도
Camera_Capture 게임 카메라의 현재 화면 캡처 ★★★★☆
SceneView_Capture2DScene Scene View를 2D 화면으로 캡처 ★★★★☆
SceneView_CaptureMultiAngleSceneView Scene을 여러 각도에서 캡처 ★★★☆☆
GetProjectData Unity 버전·렌더 파이프라인 등 프로젝트 정보 조회 ★★★★★
GetUserGuidelines 프로젝트에 설정된 사용자 작업 지침 조회 ★★★★☆
PackageManager_ExecuteAction Unity 패키지 설치·제거·업데이트 실행 ★★★★☆
PackageManager_GetData 설치된 패키지와 사용 가능한 버전 조회 ★★★★☆
활용도: 일반적인 Unity 프로젝트에서 AI 에이전트가 실제 개발 작업에 사용하는 빈도 기준

'Unity > AI' 카테고리의 다른 글

github copilot과 Unity MCP 연결(Claude Code 도 비슷)  (0) 2026.05.25
Unity MCP Tools 설명  (0) 2026.05.24

Unity uGUI Safe Area로 안전한 모바일 UI 만들기

한 줄 소개: 모바일 기기의 노치, 둥근 모서리, 시스템 제스처 영역을 피해 UI를 배치하는 uGUI Safe Area 적용법입니다.

목표

노치나 화면 모서리 때문에 버튼, HUD, 상단 바가 가려지지 않도록 필수 UI를 안전 영역 안에 배치합니다.

해상도와 화면 방향이 바뀌어도 같은 UI 구조가 자동으로 대응하도록 구성합니다.

 

유니티 : 6.6
패키지 버전: uGUI 2.6.0 (패키지 매니저에서 설치)
날짜: 2026년 3월 30일

(오랜기간 직접 개발해서 썼는데 드디어 지원. 울고 싶다. )

결과

UI가 다양한 모바일 화면에서 잘리지 않고 안정적으로 표시됩니다.

핵심 요약

  • Safe Area 컴포넌트는 기기가 알려 주는 Screen.safeArea를 기준으로 RectTransform의 앵커를 조정합니다.
  • 해상도나 화면 방향이 바뀌면 안전 영역도 런타임에 다시 반영됩니다.
  • 배경처럼 화면 끝까지 보여야 하는 요소는 Safe Area 밖에 두고, 버튼과 HUD처럼 반드시 보여야 하는 요소만 안에 둡니다.
  • 한쪽 노치 때문에 UI가 치우치면 Center Horizontally 또는 Center Vertically를 사용합니다.

적용 방법

1. 전용 루트 오브젝트 만들기

Canvas 아래에 화면 전체를 채우는 SafeAreaRoot를 만들고 Safe Area 컴포넌트를 추가합니다.

 

빨간색은 SafeArea

파란색은 TopBar

중앙.

초록색은 BottomBar

빨간색은 SafeArea

SafeAreaTestCanvas.zip
0.10MB

2. 필수 UI를 자식으로 이동하기

화면에서 반드시 보여야 하는 버튼, 텍스트, HUD를 SafeAreaRoot 아래에 둡니다.

전체 화면을 채울 배경과 장식 이미지는 Canvas 바로 아래에 유지합니다.

3. Inspector 설정하기

  1. Reference Orientation을 UI를 제작한 기준 방향으로 설정합니다.
  2. Edges에서 안전 영역을 적용할 위·오른쪽·아래·왼쪽 가장자리를 선택합니다.
  3. 가로 화면에서 한쪽 노치 때문에 UI가 치우치면 Center Horizontally를 켭니다.
  4. 위아래 여백이 비대칭이라면 Center Vertically를 켭니다.

참고 자료

 

----

 

 

'Unity > UI' 카테고리의 다른 글

RectTransformUtility  (0) 2023.03.14
CalculateRelativeRectTransformBounds  (1) 2021.08.04
텍스트 배경크기조절. ContentSizeFitter  (0) 2021.08.04
글자수에 따라 배경의 크기가 자동으로 커지는 효과  (0) 2020.08.11
팁 정리  (0) 2019.10.30

기본 / 생성

옵션뜻입력 예시

Enabled 자동 생성 여부 true
Rate 초당 생성 개수 20
Lifetime 파티클 생존 시간 1 ~ 2
TimeScale 파티클 시간 배율 1

외형

옵션뜻입력 예시

Texture 파티클 이미지 rbxassetid://123
Size 수명에 따른 크기 변화 0 → 2 → 0
Squash 가로/세로 눌림·늘림 0 → 1
Color 수명에 따른 색 변화 흰색 → 파랑
Transparency 수명에 따른 투명도 1 → 0 → 1
Brightness 파티클 밝기 1
LightEmission 자체 발광 느낌 0.8
LightInfluence 주변 조명의 영향 0
ZOffset 화면상 앞뒤 렌더 위치 0.1

이동

옵션뜻입력 예시

Speed 처음 이동 속도 5 ~ 10
Acceleration 생성 후 계속 받는 가속도 0, 10, 0
Drag 이동 감속 정도 3
VelocityInheritance 부모 이동속도 상속 비율 0.5
LockedToPart 생성 후에도 부모를 따라갈지 false
WindAffectsDrag 월드 바람 영향 여부 true

방출 방향

옵션뜻입력 예시

EmissionDirection 기본 방출 방향 Top
SpreadAngle 방출되는 퍼짐 각도 30, 30

EmissionDirection

Top
Bottom
Front
Back
Left
Right

회전

옵션뜻입력 예시

Rotation 생성될 때 시작 회전각 0 ~ 360
RotSpeed 초당 회전 속도 -90 ~ 90

파티클 방향

옵션뜻입력 예시

Orientation 파티클 이미지가 바라보는 방향 FacingCamera

Orientation

FacingCamera              카메라를 바라봄
FacingCameraWorldUp       세로 방향을 유지하며 카메라를 봄
VelocityParallel          이동 방향과 평행
VelocityPerpendicular     이동 방향과 수직

생성 영역 Shape

옵션뜻입력 예시

Shape 파티클 생성 영역 모양 Sphere
ShapeStyle 영역 내부/표면 생성 Volume
ShapeInOut 안쪽/바깥쪽 방출 Outward
ShapePartial Shape 일부만 사용 0.5

Shape

Box
Sphere
Cylinder
Disc

ShapeStyle

Volume     내부 전체에서 생성
Surface    표면에서만 생성

ShapeInOut

Outward     바깥쪽으로
Inward      안쪽으로
InAndOut    양쪽 랜덤

Flipbook 애니메이션

옵션뜻입력 예시

FlipbookLayout 스프라이트 프레임 배열 Grid4x4
FlipbookSizeX Custom 가로 프레임 수 4
FlipbookSizeY Custom 세로 프레임 수 4
FlipbookFramerate 애니메이션 FPS 12
FlipbookMode 애니메이션 재생 방식 Loop
FlipbookStartRandom 랜덤 프레임부터 시작 true
FlipbookBlendFrames 프레임 사이 부드럽게 보간 true

FlipbookLayout

None
Grid2x2
Grid4x4
Grid8x8
Custom

FlipbookMode

Loop       반복
OneShot    한 번 재생
PingPong   정방향 → 역방향 반복
Random     랜덤

거의 안 쓰는 옵션

옵션뜻입력 예시

LocalTransparencyModifier 로컬 투명도 추가 보정 0
VelocitySpread 구형 퍼짐 옵션, Deprecated 사용 안 함
FlipbookIncompatible Flipbook 사용 불가 상태 확인 읽기용

가장 자주 쓰는 핵심만 뽑으면

Rate                초당 생성량              20
Lifetime            생존시간                 1 ~ 2
Size                크기 변화                0 → 2 → 0
Transparency        투명도 변화              1 → 0 → 1
Speed               초기 속도                5 ~ 10
Acceleration        가속도                   0, 10, 0
Drag                감속                     3
EmissionDirection   방출 방향                Top
SpreadAngle         퍼짐 각도                30, 30
Rotation            시작 회전                0 ~ 360
RotSpeed            회전 속도                -90 ~ 90
Color               색 변화                  흰색 → 파랑
Texture             이미지                   rbxassetid://123

 

트레일 텍스쳐 옵션정리

'로블록스' 카테고리의 다른 글

데이터 저장소 4.(캐싱)  (0) 2026.07.30
데이터 저장소. 3 (버전)  (0) 2026.07.30
데이터 저장소. 2 (웹페이지에서 접근)  (0) 2026.07.30
데이터 저장소. 1 ( 기본 )  (0) 2026.07.30
카메라 변경  (0) 2026.07.26

캐싱

성능을 개선하고 서버에 대한 요청 수를 줄이기 위해 데이터 저장소의 데이터를 임시로 저장하는 데 캐싱을 사용하세요. 예를 들어, 게임은 데이터의 복사본을 캐시하여 데이터 저장소에 다른 호출을 할 필요 없이 빠르게 접근할 수 있습니다.

 

실제 구현은?

Roblox 데이터 저장소(DataStore)에서 GetAsync(), SetAsync(), UpdateAsync(), IncrementAsync(), RemoveAsync() 등의 작업 명령어를 호출하면, 기본적으로 엔진 차원에서 4초간의 로컬 캐싱이 자동으로 작동합니다.

따라서 개발자가 별도의 캐시 시스템을 구현하지 않아도 데이터 저장소를 사용하는 것만으로 캐싱이 자연스럽게 적용됩니다.

캐싱 비활성화 

local ok = pcall(function()
    store:UpdateAsync(key, transform)
end)

if not ok then
    local options = Instance.new("DataStoreGetOptions")
    options.UseCache = false

    local success, value = pcall(function()
        return store:GetAsync(key, options)
    end)

    if success then
        -- 캐시된 상태가 아닌 백엔드 상태에 따라 결정
    end
end

'로블록스' 카테고리의 다른 글

파티클  (0) 2026.08.22
데이터 저장소. 3 (버전)  (0) 2026.07.30
데이터 저장소. 2 (웹페이지에서 접근)  (0) 2026.07.30
데이터 저장소. 1 ( 기본 )  (0) 2026.07.30
카메라 변경  (0) 2026.07.26

+ Recent posts