5주차 2교시 / 이론과 설명

Tilemap 위에
시스템 세우기

타일은 공간을 빠르게 반복하고, Prefab은 행동을 정확히 반복합니다. 두 도구의 책임을 나누면 수집, 위험, 목표가 하나의 작은 레벨로 연결됩니다.

어두운 격자 보드 위에 반복 타일로 만든 작은 길과 수집물 세 개, 위험 구역 두 개, 목표 문이 있고 옆에는 바닥, 충돌, 상호작용 층이 분리되어 있는 장면
공간과 행동의 분리 Tilemap은 셀 단위 공간을, Prefab은 반복 가능한 상호작용 묶음을 담당합니다.

2교시의 도착점

학생은 교수자의 Scene 구조와 완성 코드를 보며 “무엇을 어디에 두고, 어떤 사건이 누구에게 전달되는가”를 설명합니다. 실제 레벨 제작은 3교시에 시작합니다.

  • 형식교수자 이론과 시연
  • 공간 구조Grid / Tilemap
  • 행동 구조Prefab / Script
  • 01

    Grid, Tile Asset, Tile Palette, Tilemap의 역할을 한 문장씩 구분합니다.

  • 02

    Ground, Collision, Interaction을 분리해 그림, 물리, 사건의 책임을 보이게 만듭니다.

  • 03

    Prefab을 같은 Component와 설정을 가진 반복 가능한 원본으로 설명합니다.

  • 04

    네 Script의 callback이 하나의 PlayerProgress로 모이는 흐름을 추적합니다.

  1. Trigger 회수

    접촉 후보가 callback이 되기까지 네 조건을 그림 없이 복원합니다.

  2. Tilemap 어휘

    Grid부터 Tilemap Renderer까지 편집 도구와 실행 결과를 구분합니다.

  3. 세 Layer 구조

    Ground, Collision, Interaction을 나눈 Scene Hierarchy를 해설합니다.

  4. Prefab과 Matrix

    반복 행동의 원본과 필요한 접촉 조합만 남기는 필터를 연결합니다.

  5. 전체 코드 추적

    수집, 리셋, 목표 판정이 PlayerProgress를 공유하는 흐름을 읽습니다.

  6. 상태 시나리오

    수집 전과 후, 위험 접촉 뒤의 결과를 실행 전에 예측합니다.

Tilemap은 그림 한 장이 아니라 셀 데이터입니다

완성 화면만 보면 바닥이 하나의 이미지처럼 보입니다. 실제로는 Grid의 좌표 위에 “어느 셀에 어떤 Tile을 놓았는가”가 저장되고, Renderer와 Collider가 그 데이터를 각자의 방식으로 읽습니다.

Grid

셀 좌표의 기준

여러 Tilemap이 같은 셀 크기와 정렬 규칙을 공유하도록 좌표 틀을 제공합니다. 보통 Tilemap을 만들면 부모에 함께 생깁니다.

Tile Asset

한 칸의 데이터

한 셀에 표시할 Sprite와 색, Collider 형태 같은 정보를 담습니다. Scene의 GameObject가 아니라 Project의 Asset입니다.

Tile Palette

편집용 도구함

사용할 Tile을 모아 두고 붓, 상자, 지우개로 Tilemap에 칠하게 해 줍니다. 게임이 실행될 때 화면에 보이는 UI는 아닙니다.

Tilemap

셀 배치 기록

어느 좌표에 어떤 Tile이 놓였는지 저장하는 Component입니다. Ground와 Collision을 별도 GameObject로 나누면 책임이 선명해집니다.

Tilemap Renderer

셀을 화면에 그림

Tilemap의 Sprite를 한꺼번에 렌더링합니다. Sorting Layer와 Order로 다른 Sprite 앞뒤 관계를 정합니다.

Tilemap Collider 2D

셀을 물리 형상으로 바꿈

Tile의 Collider Type을 읽어 각 셀에 Collider 형상을 만듭니다. 벽처럼 막아야 하는 Tilemap에만 둡니다.

편집 도구와 실행 Component를 나눕니다

Tile Palette는 제작자가 칠할 때 사용하는 창입니다. 게임 속 레벨은 Grid와 Tilemap Component에 저장됩니다. 따라서 Palette 창을 닫아도 이미 칠한 레벨은 사라지지 않습니다.

Retrieval 01

Tile을 골라 셀에 칠할 때 직접 사용하는 것은?

편집 작업의 역할을 고릅니다.

그림, 물리, 사건을 서로 다른 층에 둡니다

모든 Tile을 한 Tilemap에 칠하면 처음에는 빨라 보입니다. 그러나 무엇이 막고 무엇이 장식인지 찾기 어려워집니다. 책임별 GameObject를 나누면 Scene을 보는 것만으로 수정 위치가 드러납니다.

01 / VISUALGround Tilemap

바닥과 길을 그립니다. Tilemap Renderer는 사용하지만 Collider는 두지 않습니다. Player는 이 그림 위를 자유롭게 이동합니다.

02 / PHYSICSCollision Tilemap

벽과 경계를 칠합니다. Tilemap Collider 2D가 셀을 막는 형상으로 바꾸며 Environment Layer에 둡니다.

사건Interactions

Collectible, HazardZone, GoalZone Prefab Instance를 담는 빈 부모입니다. 각각 Is Trigger가 켜진 Collider2D와 Script를 가집니다.

Collision Tilemap

많이 반복되는 공간

격자에 맞춘 벽처럼 동일한 규칙이 넓게 반복될 때 유리합니다. 여러 셀의 Collider를 자동으로 관리합니다.

Interaction Prefab

의미가 있는 개별 사건

수집, 위험, 목표처럼 각 Instance가 상태와 행동을 가질 때 유리합니다. 위치는 달라도 같은 Component 구성을 공유합니다.

Tilemap Collider 2D는 필요한 곳에만 둡니다

Ground까지 Collider를 가지면 Player가 이동해야 할 바닥이 장애물이 될 수 있습니다. 이번 주에는 Collision Tilemap 하나만 물리 벽을 담당하게 해 원인을 좁힙니다. 큰 Tilemap의 Collider 형상을 단순화하는 Composite Collider 2D는 원리를 이해한 뒤 선택적으로 사용합니다.

Retrieval 02

Tilemap Collider 2D가 필요한 곳은?

이번 주의 역할 분리 원칙을 적용합니다.

Prefab은 모양보다 행동 묶음을 반복합니다

수집물 세 개를 복사하는 목적은 같은 Sprite를 얻는 데 그치지 않습니다. Collider의 크기, Is Trigger, Layer, Script와 설정값까지 하나의 검증된 원본에서 반복하는 것이 핵심입니다.

PF_Collectible

정확히 세 Instance

Sprite Renderer, Circle Collider 2D, Collectible Script를 가집니다. 한 번 수집되면 PlayerProgress에 1을 전달하고 자신을 비활성화합니다.

PF_HazardZone

정확히 두 Instance

구역을 보여 주는 Sprite, Box Collider 2D, HazardZone Script를 가집니다. Player를 시작 위치로 돌려보냅니다.

PF_GoalZone

정확히 한 Instance

목표 문 Sprite, Box Collider 2D, GoalZone Script를 가집니다. PlayerProgress에 완료 조건을 질문합니다.

Player

상태의 유일한 소유자

4주차 이동 Component에 PlayerProgress를 추가합니다. 수집 개수와 시작 위치를 한곳에서 관리해 Trigger끼리 직접 의존하지 않게 합니다.

LayerPlayerEnvironmentInteraction
Player×
Environment××
Interaction××

이 표는 이번 레벨에 필요한 접촉만 남긴 개념 모델입니다. 실제 Matrix는 대칭이므로 한 Layer 조합을 바꾸면 반대쪽 관계도 함께 바뀝니다. Player는 벽과 부딪히고 Interaction을 감지하지만, 벽과 수집물끼리 서로 사건을 만들 필요는 없습니다.

Retrieval 03

수집물 세 개의 Collider 크기를 함께 수정하려면?

반복 가능한 원본의 장점을 고릅니다.

세 Trigger가 하나의 Player 상태를 공유합니다

각 Trigger는 자신이 감지한 사건만 해석합니다. 수집 개수와 시작 위치는 PlayerProgress 한 곳에 두고, 다른 Script는 필요한 public method만 요청합니다.

01 / STOREPlayerProgress

필요 개수, 현재 개수와 시작 위치를 기억합니다.

02 / ADDCollectible

Player를 확인하고 수집 개수를 한 번 올립니다.

03 / RESETHazardZone

Player를 시작 위치로 돌려보냅니다.

04 / ASKGoalZone

필요 개수를 모았는지 질문하고 성공을 판정합니다.

PlayerProgress.cs / 상태의 단일 소유자
using UnityEngine;

public class PlayerProgress : MonoBehaviour
{
    [SerializeField, Min(1)]
    private int requiredCollectibles = 3;

    private int collectedCount;
    private Rigidbody2D body;
    private Vector2 startPosition;

    private void Awake()
    {
        body = GetComponent<Rigidbody2D>();
    }

    private void Start()
    {
        startPosition = body.position;
    }

    public void AddCollectible()
    {
        collectedCount++;
        Debug.Log("수집: " + collectedCount + " / " + requiredCollectibles);
    }

    public bool HasAllCollectibles()
    {
        return collectedCount >= requiredCollectibles;
    }

    public void ResetToStart()
    {
        body.linearVelocity = Vector2.zero;
        body.position = startPosition;
    }
}
  • private field상태는 PlayerProgress 안에서만 직접 바꿉니다. Inspector에 보여야 하는 필요 개수만 SerializeField로 노출합니다.
  • AddCollectible()Collectible은 내부 field를 알지 않고 “하나를 더하라”는 요청만 보냅니다.
  • HasAllCollectibles()GoalZone은 계산식을 복제하지 않고 bool 질문 하나로 완료 조건을 확인합니다.
  • ResetToStart()위험 접촉은 Rigidbody2D의 속도를 0으로 만들고 물리 위치를 시작점으로 옮깁니다. 수집 개수는 유지됩니다.
Collectible.cs / 한 번만 보고
using UnityEngine;

public class Collectible : MonoBehaviour
{
    private bool collected;

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (collected || !other.CompareTag("Player"))
        {
            return;
        }

        if (!other.TryGetComponent(out PlayerProgress progress))
        {
            return;
        }

        collected = true;
        progress.AddCollectible();
        gameObject.SetActive(false);
    }
}
HazardZone.cs / 속도와 물리 위치 초기화
using UnityEngine;

public class HazardZone : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (!other.CompareTag("Player"))
        {
            return;
        }

        if (other.TryGetComponent(out PlayerProgress progress))
        {
            progress.ResetToStart();
        }
    }
}
GoalZone.cs / 조건을 물어본 뒤 판정
using UnityEngine;

public class GoalZone : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (!other.CompareTag("Player"))
        {
            return;
        }

        if (!other.TryGetComponent(out PlayerProgress progress))
        {
            return;
        }

        if (!progress.HasAllCollectibles())
        {
            Debug.Log("아직 수집물이 남았습니다.");
            return;
        }

        Debug.Log("LEVEL COMPLETE");
    }
}

TryGetComponent가 두 가지를 동시에 확인합니다

상대 GameObject에 PlayerProgress가 있는지 확인하고, 있다면 같은 줄에서 progress 변수로 받아 옵니다. 이번 구성에서는 Player Tag와 PlayerProgress가 같은 Player GameObject에 있어야 합니다.

화면보다 먼저 상태 변화를 예측합니다

같은 Scene도 접촉 순서에 따라 결과가 달라집니다. 교수자는 Play 전에 아래 시나리오를 제시하고, 학생에게 어떤 field와 위치가 바뀌는지 먼저 말하게 합니다.

SCENARIO A

0개 상태에서 Goal 접촉

HasAllCollectibles()가 false를 반환합니다. 완료되지 않고 “아직 수집물이 남았습니다”가 기록됩니다.

SCENARIO B

수집물 하나를 두 번 통과

첫 접촉 뒤 GameObject가 비활성화되고 collected 방어선도 생깁니다. 개수는 한 번만 증가합니다.

SCENARIO C

2개 수집 후 Hazard 접촉

Player 위치는 시작점으로 돌아가지만 collectedCount는 2를 유지합니다. 남은 하나만 찾으면 됩니다.

SCENARIO D

3개 수집 후 Goal 접촉

3 >= 3이 true가 되어 완료 메시지가 한 번 기록됩니다. 레벨의 최소 목표가 성립합니다.

시연에서 확인할 장면

  1. Hierarchy부터 읽기

    Grid 아래 두 Tilemap과 Interactions 부모를 접은 상태에서도 책임이 보이는지 확인합니다.

  2. Prefab 원본 수정

    Collectible Collider 반지름을 바꾸고 세 Instance가 같은 변경을 이어받는 모습을 보여 줍니다.

  3. Matrix 한 칸 끄기

    Player와 Interaction 조합을 끄면 코드가 같아도 모든 Trigger 사건이 사라짐을 비교합니다.

  4. 상태 순서 실행

    Goal 거절 → 수집 2 → Hazard → 수집 1 → Goal 성공 순서로 Console과 위치를 추적합니다.

Unity 6.6 공식 문서

Tilemap과 Prefab의 정확한 역할, Collider 생성 방식과 Component 탐색 동작은 아래 공식 문서를 기준으로 정리했습니다.

Reuse

Prefabs ↗

Component, property와 child를 재사용 가능한 Asset 원본으로 저장하는 기능

질문이 남았다면

“Collision Tilemap의 벽은 Player를 막지만 Collectible callback만 오지 않습니다. 두 Collider와 Player Rigidbody2D, Is Trigger까지 확인했습니다”처럼 정상인 층과 실패한 층을 나누어 말합니다.