🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

타일맵 시스템 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 29. · 2 Views

타일맵 시스템 완벽 가이드

Flame 게임 엔진에서 타일맵을 활용하여 효율적으로 게임 월드를 구성하는 방법을 배웁니다. Tiled 에디터부터 충돌 처리, 동적 타일 변경까지 실무에서 바로 활용할 수 있는 내용을 다룹니다.


목차

  1. Tiled 맵 에디터 소개
  2. 타일셋 준비하기
  3. flame_tiled 패키지
  4. 타일맵 로딩
  5. 충돌 레이어 설정
  6. 동적 타일 변경

1. Tiled 맵 에디터 소개

어느 날 김게임 씨가 2D 게임을 개발하다가 고민에 빠졌습니다. 맵을 만들려고 하는데 타일 하나하나의 좌표를 계산해서 코드로 배치하려니 너무 비효율적이었습니다.

"이걸 언제 다 만들지?" 막막함이 밀려왔습니다.

Tiled는 2D 게임 맵을 시각적으로 제작할 수 있는 무료 맵 에디터입니다. 마치 포토샵으로 이미지를 그리듯이, 타일을 배치하여 게임 월드를 디자인할 수 있습니다.

코드 없이 드래그 앤 드롭만으로 복잡한 맵을 만들 수 있어 개발 시간을 크게 단축시켜 줍니다.

다음 코드를 살펴봅시다.

// pubspec.yaml에 flame_tiled 추가
dependencies:
  flame: ^1.10.0
  flame_tiled: ^1.15.0

// Tiled 맵 파일(.tmx) 경로 설정
class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    // assets/maps/level1.tmx 파일을 로드합니다
    final tiledMap = await TiledComponent.load(
      'level1.tmx',
      Vector2.all(16), // 타일 크기 16x16
    );
    add(tiledMap);
  }
}

김게임 씨는 입사 2개월 차 게임 개발자입니다. 첫 프로젝트로 2D 플랫포머 게임을 만들게 되었는데, 가장 먼저 부딪힌 문제가 바로 맵 제작이었습니다.

"타일 하나하나를 코드로 배치하려니 너무 복잡해요." 선배 개발자 박게임 씨가 웃으며 다가왔습니다. "그래서 우리는 Tiled를 사용하죠.

한번 배워보면 게임 개발이 훨씬 쉬워질 거예요." Tiled 맵 에디터란 정확히 무엇일까요? 쉽게 비유하자면, Tiled는 마치 레고 블록을 조립하듯이 게임 맵을 만드는 도구입니다.

레고를 조립할 때 설명서를 보며 블록을 끼우듯이, Tiled에서는 타일 이미지를 격자판에 배치하여 맵을 완성합니다. 일일이 좌표를 계산할 필요가 없습니다.

왜 이런 도구가 필요할까요? 초창기 게임 개발에서는 모든 타일의 위치를 코드로 작성해야 했습니다.

"1번 타일은 (0, 0)에, 2번 타일은 (16, 0)에" 이런 식으로 수백 개의 타일을 배치하려면 며칠이 걸렸습니다. 더 큰 문제는 맵을 수정할 때였습니다.

하나의 타일을 옮기려면 코드를 찾아서 좌표를 다시 계산해야 했습니다. 또한 디자이너와 개발자의 협업이 어려웠습니다.

디자이너가 맵 아이디어를 내면 개발자가 일일이 코드로 옮겨야 했습니다. 의사소통 과정에서 오류가 생기기 쉬웠고, 수정 사항을 반영하는 데도 시간이 오래 걸렸습니다.

바로 이런 문제를 해결하기 위해 Tiled가 등장했습니다. Tiled를 사용하면 시각적으로 맵을 편집할 수 있습니다.

마우스로 클릭하고 드래그하면 타일이 배치됩니다. 실시간으로 결과를 확인하면서 작업할 수 있어 생산성이 크게 향상됩니다.

또한 레이어 시스템을 제공하여 배경, 오브젝트, 충돌 영역을 분리해서 관리할 수 있습니다. Tiled는 완전히 무료이며 Windows, Mac, Linux 모두에서 사용할 수 있습니다.

공식 홈페이지(mapeditor.org)에서 다운로드받을 수 있으며, 설치도 간단합니다. 설치 후 Tiled를 실행하면 직관적인 인터페이스가 나타납니다.

왼쪽에는 타일셋 패널, 중앙에는 맵 편집 영역, 오른쪽에는 레이어와 속성 패널이 배치되어 있습니다. 포토샵이나 일러스트레이터를 사용해본 적이 있다면 금방 익숙해질 수 있습니다.

맵을 만들 때는 먼저 새 맵을 생성합니다. 맵의 크기(가로 세로 타일 개수)와 타일의 크기(픽셀 단위)를 설정합니다.

일반적으로 16x16이나 32x32 픽셀 크기를 많이 사용합니다. 그다음 타일셋 이미지를 불러와 등록하면 준비가 완료됩니다.

실제 작업은 매우 직관적입니다. 타일셋에서 원하는 타일을 선택하고, 맵 영역에 클릭하거나 드래그하면 타일이 배치됩니다.

지우개 도구로 타일을 삭제할 수도 있고, 사각형 선택으로 여러 타일을 한 번에 배치할 수도 있습니다. 작업이 끝나면 TMX 형식으로 저장합니다.

이 파일은 XML 기반의 텍스트 파일로, Flame 엔진에서 바로 읽어들일 수 있습니다. 코드 몇 줄만 작성하면 게임에 맵이 나타납니다.

박게임 씨의 설명을 들은 김게임 씨는 눈이 반짝였습니다. "이렇게 쉬운 방법이 있었다니!" 당장 Tiled를 설치하고 첫 맵을 만들어보기로 했습니다.

Tiled를 제대로 활용하면 맵 제작 시간을 90% 이상 줄일 수 있습니다. 게임 개발의 핵심은 재미있는 콘텐츠를 만드는 것이지, 타일 좌표를 계산하는 것이 아닙니다.

여러분도 오늘 배운 내용을 바탕으로 Tiled를 설치하고 간단한 맵을 만들어보세요.

실전 팁

💡 - Tiled 공식 튜토리얼(docs.mapeditor.org)을 먼저 읽어보면 기능을 빠르게 익힐 수 있습니다

  • 레이어 이름을 명확하게 지정하면 나중에 코드에서 찾기 쉽습니다(예: background, objects, collision)

2. 타일셋 준비하기

김게임 씨가 Tiled를 설치하고 신나게 맵을 만들려는데, 문제가 생겼습니다. "타일셋 이미지는 어떻게 준비해야 하죠?" 아무 이미지나 사용하면 될 줄 알았는데, 생각보다 규칙이 까다로웠습니다.

타일셋은 게임에 사용될 모든 타일 이미지를 하나의 이미지 파일에 격자 형태로 배치한 것입니다. 마치 우표 시트처럼 여러 개의 작은 이미지를 규칙적으로 배열합니다.

올바른 타일셋 구조를 갖추면 Tiled와 Flame에서 문제없이 사용할 수 있습니다.

다음 코드를 살펴봅시다.

// 타일셋 이미지 구조 예시 (주석으로 표현)
// tileset.png: 256x256 픽셀 이미지
// 각 타일: 16x16 픽셀
// 총 16x16 = 256개의 타일 포함

// assets 폴더 구조
// assets/
//   images/
//     tileset.png          // 타일셋 이미지
//   maps/
//     level1.tmx           // Tiled 맵 파일
//     tileset.tsx          // 타일셋 정의 파일 (Tiled가 자동 생성)

// pubspec.yaml 설정
flutter:
  assets:
    - assets/images/
    - assets/maps/

박게임 씨가 김게임 씨의 화면을 보고 고개를 저었습니다. "타일셋 이미지가 잘못되었네요.

타일 크기가 일정하지 않아서 Tiled가 제대로 인식하지 못할 거예요." 타일셋을 준비할 때는 몇 가지 중요한 규칙을 지켜야 합니다. 타일셋 이미지란 무엇일까요?

쉽게 비유하자면, 타일셋은 마치 우표 시트와 같습니다. 우표 시트를 보면 여러 개의 우표가 격자 형태로 반듯하게 배열되어 있습니다.

각 우표의 크기가 동일하고, 간격도 일정합니다. 타일셋도 마찬가지입니다.

여러 개의 타일 이미지를 격자 형태로 배열한 하나의 큰 이미지 파일입니다. 왜 이런 형식이 필요할까요?

예전에는 타일마다 개별 이미지 파일을 사용했습니다. grass.png, stone.png, water.png 이런 식으로 수백 개의 파일을 관리해야 했습니다.

파일이 많아질수록 로딩 시간이 길어졌고, 파일 관리도 복잡해졌습니다. 하나의 타일을 찾으려면 폴더를 한참 뒤져야 했습니다.

또한 게임 엔진 입장에서도 비효율적이었습니다. 이미지를 로드할 때마다 파일 시스템에 접근해야 했고, GPU 메모리도 낭비되었습니다.

성능 문제가 심각했습니다. 바로 이런 문제를 해결하기 위해 타일셋 시스템이 등장했습니다.

타일셋을 사용하면 하나의 이미지 파일만 로드하면 됩니다. 파일 입출력 횟수가 줄어들어 로딩 속도가 빨라집니다.

또한 텍스처 아틀라스 기법을 활용하여 GPU 메모리를 효율적으로 사용할 수 있습니다. 같은 타일셋 내의 타일들은 하나의 텍스처로 처리되어 렌더링 성능이 향상됩니다.

타일셋을 만들 때 가장 중요한 규칙은 일정한 타일 크기입니다. 모든 타일이 정확히 같은 크기여야 합니다.

예를 들어 16x16 픽셀로 정했다면, 모든 타일이 정확히 16x16 픽셀이어야 합니다. 하나라도 크기가 다르면 Tiled가 제대로 인식하지 못합니다.

두 번째 규칙은 격자 정렬입니다. 타일들은 규칙적인 격자 형태로 배열되어야 합니다.

가로로 10개, 세로로 10개씩 배열한다면 총 100개의 타일이 정확한 위치에 배치되어야 합니다. 픽셀 하나라도 어긋나면 안 됩니다.

세 번째 규칙은 여백과 간격입니다. 타일 사이에 여백이 있을 수도 있고 없을 수도 있습니다.

중요한 것은 Tiled 설정에서 정확히 지정해주어야 한다는 점입니다. 여백이 2픽셀이라면 설정에서도 2픽셀로 입력해야 합니다.

타일셋 이미지를 직접 만들 수도 있지만, 초보자에게는 어려울 수 있습니다. 다행히 무료 타일셋을 제공하는 사이트들이 많습니다.

itch.io, OpenGameArt.org, Kenney.nl 등에서 고품질 타일셋을 무료로 다운로드할 수 있습니다. 라이선스를 확인하고 사용하면 됩니다.

타일셋 이미지를 준비했다면 Tiled에서 불러옵니다. 메뉴에서 "새 타일셋"을 선택하고, 이미지 파일을 지정합니다.

타일 크기(예: 16x16)를 입력하면 Tiled가 자동으로 타일을 분할합니다. 만약 여백이나 간격이 있다면 추가로 설정합니다.

타일셋이 제대로 로드되었는지 확인하는 방법은 간단합니다. 타일셋 패널에서 각 타일이 정확히 분리되어 보이는지 확인합니다.

타일이 겹치거나 잘린 부분이 있다면 설정을 다시 확인해야 합니다. Flutter 프로젝트에서는 타일셋 이미지와 TMX 파일을 모두 assets 폴더에 넣어야 합니다.

pubspec.yaml에서 assets 경로를 등록하는 것도 잊지 마세요. Flame은 assets 폴더를 기준으로 파일을 찾습니다.

김게임 씨는 무료 타일셋 사이트에서 예쁜 타일셋을 다운로드했습니다. 16x16 픽셀 크기로 깔끔하게 정렬된 타일셋이었습니다.

Tiled에서 불러오니 완벽하게 분할되었습니다. "이제 진짜 맵을 만들 수 있겠어요!" 타일셋을 제대로 준비하는 것은 타일맵 시스템의 기초입니다.

처음에는 어렵게 느껴질 수 있지만, 한 번 익숙해지면 어떤 타일셋이든 자유자재로 다룰 수 있습니다. 여러분도 무료 타일셋을 다운로드해서 직접 Tiled에서 불러와 보세요.

실전 팁

💡 - 타일셋 이미지는 2의 거듭제곱 크기(256x256, 512x512 등)로 만들면 GPU 최적화에 유리합니다

  • Tiled에서 타일셋을 외부 파일(.tsx)로 저장하면 여러 맵에서 재사용할 수 있습니다

3. flame tiled 패키지

김게임 씨가 Tiled에서 멋진 맵을 만들었습니다. 이제 이 맵을 Flutter 게임에서 사용하고 싶은데, 어떻게 해야 할까요?

"맵 파일을 어떻게 불러오죠?" 박게임 씨가 답했습니다. "flame_tiled 패키지를 사용하면 됩니다."

flame_tiled는 Tiled 맵 에디터에서 만든 TMX 파일을 Flame 게임 엔진에서 로드하고 렌더링할 수 있게 해주는 패키지입니다. 마치 번역기처럼 Tiled의 맵 데이터를 Flame이 이해할 수 있는 형태로 변환해줍니다.

몇 줄의 코드만으로 복잡한 맵을 게임에 추가할 수 있습니다.

다음 코드를 살펴봅시다.

import 'package:flame/game.dart';
import 'package:flame_tiled/flame_tiled.dart';

class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    // Tiled 맵 파일을 로드합니다
    final tiledMap = await TiledComponent.load(
      'level1.tmx',              // 맵 파일 경로
      Vector2.all(16),            // 타일 크기 (16x16)
    );

    // 맵을 게임에 추가합니다
    add(tiledMap);

    // 카메라가 맵을 따라가도록 설정
    camera.followComponent(tiledMap);
  }
}

김게임 씨는 Tiled에서 공들여 만든 맵을 보며 뿌듯해했습니다. 하지만 이 맵을 게임에 표시하려면 또 다른 작업이 필요했습니다.

"코드를 어떻게 작성해야 하죠?" 박게임 씨가 터미널을 열었습니다. "먼저 flame_tiled 패키지를 설치해야 해요." flame_tiled 패키지란 무엇일까요?

쉽게 비유하자면, flame_tiled는 마치 번역기와 같습니다. 영어 문서를 한글로 번역해주는 번역기처럼, flame_tiled는 Tiled의 TMX 파일을 Flame 엔진이 이해할 수 있는 형태로 변환해줍니다.

TMX 파일은 XML 형식의 텍스트 파일인데, 이것을 파싱하여 Flame의 컴포넌트로 만들어줍니다. 왜 이런 패키지가 필요할까요?

TMX 파일을 직접 파싱하려면 XML 구조를 이해하고, 레이어 정보를 읽고, 타일 ID를 이미지와 매칭하고, 화면에 렌더링하는 복잡한 과정을 거쳐야 합니다. 이 모든 것을 직접 구현하려면 수백 줄의 코드가 필요합니다.

버그도 생기기 쉽고, 유지보수도 어렵습니다. 또한 Tiled의 모든 기능을 지원하려면 더 많은 작업이 필요합니다.

객체 레이어, 타일 애니메이션, 타일 속성, 이미지 레이어 등 Tiled의 다양한 기능을 일일이 구현하는 것은 현실적으로 불가능합니다. 바로 이런 문제를 해결하기 위해 flame_tiled가 등장했습니다.

flame_tiled를 사용하면 단 몇 줄의 코드로 맵을 로드할 수 있습니다. TiledComponent.load 메서드 하나로 모든 작업이 완료됩니다.

레이어 파싱, 타일 렌더링, 이미지 로딩 등이 자동으로 처리됩니다. 개발자는 맵 파일 경로와 타일 크기만 지정하면 됩니다.

또한 flame_tiled는 Tiled의 모든 주요 기능을 지원합니다. 다중 레이어, 타일 애니메이션, 객체 레이어, 타일 속성 등을 모두 사용할 수 있습니다.

Tiled에서 설정한 내용이 게임에 그대로 반영됩니다. 패키지를 설치하는 방법은 간단합니다.

pubspec.yaml 파일의 dependencies 섹션에 flame_tiled를 추가하고, 터미널에서 flutter pub get을 실행하면 됩니다. 버전은 최신 안정 버전을 사용하는 것이 좋습니다.

설치가 완료되면 코드에서 import 문을 추가합니다. flame_tiled 패키지를 임포트하면 TiledComponent 클래스를 사용할 수 있습니다.

이 클래스가 핵심입니다. TiledComponent.load 메서드는 비동기 함수입니다.

맵 파일을 읽고 이미지를 로드하는 데 시간이 걸리기 때문입니다. 따라서 await 키워드와 함께 사용해야 합니다.

FlameGame의 onLoad 메서드 안에서 호출하는 것이 일반적입니다. 첫 번째 파라미터는 맵 파일의 경로입니다.

assets 폴더 기준의 상대 경로를 문자열로 전달합니다. 예를 들어 assets/maps/level1.tmx 파일이라면 'level1.tmx' 또는 'maps/level1.tmx'로 지정합니다.

두 번째 파라미터는 타일의 크기입니다. Vector2 객체로 전달하며, 보통 Vector2.all(16)처럼 가로세로 동일한 크기를 사용합니다.

이 크기는 Tiled에서 설정한 타일 크기와 일치해야 합니다. 로드가 완료되면 TiledComponent 객체가 반환됩니다.

이 객체를 add 메서드로 게임에 추가하면 맵이 화면에 나타납니다. 정말 간단하지 않나요?

김게임 씨는 코드를 작성하고 게임을 실행했습니다. "와!

맵이 나타났어요!" Tiled에서 만든 맵이 게임 화면에 정확히 표시되었습니다. 모든 레이어가 올바르게 렌더링되었고, 타일들도 제자리에 있었습니다.

박게임 씨가 웃으며 말했습니다. "flame_tiled가 없었다면 이 코드를 작성하는 데 며칠은 걸렸을 거예요." flame_tiled 패키지를 제대로 활용하면 맵 로딩 코드를 최소화할 수 있습니다.

Tiled의 강력한 기능과 Flame의 유연한 구조가 결합되어 게임 개발 생산성이 크게 향상됩니다. 여러분도 flame_tiled를 설치하고 첫 맵을 로드해 보세요.

실전 팁

💡 - flame_tiled는 Flame 버전과 호환성이 중요하므로 공식 문서에서 권장 버전을 확인하세요

  • 맵 로딩은 비동기 작업이므로 로딩 화면을 추가하면 사용자 경험이 개선됩니다

4. 타일맵 로딩

김게임 씨가 맵을 로드하는 데는 성공했지만, 게임이 시작될 때 잠깐 멈추는 현상이 발생했습니다. "왜 이러죠?" 박게임 씨가 설명했습니다.

"맵 로딩을 최적화해야 해요."

타일맵 로딩은 TMX 파일과 타일셋 이미지를 메모리에 읽어들이는 과정입니다. 마치 도서관에서 책을 찾아 책상에 올려놓는 것처럼, 파일 시스템에서 데이터를 읽어 게임 엔진이 사용할 수 있는 상태로 만듭니다.

효율적인 로딩 전략을 사용하면 게임 시작 시간을 단축하고 부드러운 경험을 제공할 수 있습니다.

다음 코드를 살펴봅시다.

class GameLoadingState extends FlameGame {
  late TiledComponent map;

  @override
  Future<void> onLoad() async {
    // 1. 이미지 사전 로딩 (캐싱)
    await images.loadAll([
      'tileset.png',
      'background.png',
    ]);

    // 2. 맵 로딩
    map = await TiledComponent.load(
      'level1.tmx',
      Vector2.all(16),
    );

    // 3. 맵 위치 설정
    map.position = Vector2.zero();

    // 4. 게임에 추가
    add(map);

    // 5. 로딩 완료 로그
    print('맵 로딩 완료: ${map.tileMap.map.width}x${map.tileMap.map.height}');
  }
}

김게임 씨의 게임이 시작될 때 화면이 1초 정도 멈췄습니다. 사용자 입장에서는 게임이 버그가 있는 것처럼 느껴질 수 있었습니다.

"이 문제를 어떻게 해결하죠?" 박게임 씨가 코드를 살펴보더니 고개를 끄덕였습니다. "맵 로딩 과정을 이해하면 최적화할 수 있어요." 타일맵 로딩이란 정확히 무엇일까요?

쉽게 비유하자면, 타일맵 로딩은 마치 도서관에서 책을 빌리는 과정과 같습니다. 먼저 목록(TMX 파일)을 확인하고, 책이 어디 있는지 찾아(파일 시스템 탐색), 책을 책장에서 꺼내(파일 읽기), 책상에 올려놓습니다(메모리 로드).

각 단계마다 시간이 걸립니다. 왜 로딩 시간이 발생할까요?

TMX 파일은 XML 형식의 텍스트 파일입니다. 이 파일을 읽어서 파싱하는 과정이 필요합니다.

파일 크기가 클수록, 레이어가 많을수록 파싱 시간이 길어집니다. 수천 개의 타일 정보를 읽고 해석해야 하기 때문입니다.

또한 타일셋 이미지를 로드하는 시간도 상당합니다. 이미지 파일을 디스크에서 읽고, 디코딩하고, GPU 메모리에 업로드하는 과정이 모두 필요합니다.

고해상도 이미지일수록 시간이 더 걸립니다. 문제는 이 모든 과정이 동기적으로 실행되면 게임이 멈춘다는 것입니다.

로딩이 완료될 때까지 화면이 업데이트되지 않고, 사용자 입력도 처리되지 않습니다. 사용자는 게임이 멈춘 것으로 오해할 수 있습니다.

바로 이런 문제를 해결하기 위해 비동기 로딩사전 로딩 전략을 사용합니다. 비동기 로딩은 async/await 키워드를 활용합니다.

TiledComponent.load는 이미 비동기 함수이므로, onLoad 메서드를 async로 선언하고 await로 호출하면 됩니다. 이렇게 하면 Flame 엔진이 로딩 과정을 백그라운드에서 처리하고, 필요하면 로딩 화면을 표시할 수 있습니다.

사전 로딩은 이미지를 미리 캐시에 올려두는 전략입니다. Flame의 images.loadAll 메서드를 사용하면 여러 이미지를 한 번에 로드할 수 있습니다.

타일맵을 로드하기 전에 타일셋 이미지를 미리 로드하면, 실제 맵 로딩 시간이 단축됩니다. 로딩 순서도 중요합니다.

일반적으로 다음 순서를 권장합니다. 첫째, 필수 이미지를 사전 로드합니다.

둘째, TMX 파일을 로드합니다. 셋째, 맵 컴포넌트를 게임에 추가합니다.

이 순서를 지키면 에러를 방지할 수 있습니다. 맵 로딩이 완료되면 TiledComponent 객체를 받습니다.

이 객체는 tileMap 속성을 통해 맵의 메타데이터에 접근할 수 있습니다. 맵의 가로세로 크기, 레이어 목록, 타일셋 정보 등을 확인할 수 있습니다.

맵의 위치를 설정하는 것도 잊지 마세요. 기본적으로 맵은 (0, 0) 위치에 배치되지만, 필요하면 position 속성으로 위치를 조정할 수 있습니다.

카메라의 초기 위치도 맵에 맞춰 설정하면 좋습니다. 대형 맵의 경우 청크(chunk) 단위로 나누어 로드하는 기법도 있습니다.

현재 화면에 보이는 부분만 로드하고, 플레이어가 이동하면 추가로 로드하는 방식입니다. 하지만 대부분의 2D 게임에서는 전체 맵을 한 번에 로드하는 것이 일반적입니다.

김게임 씨는 코드를 수정하여 이미지를 사전 로드하고, 로딩 완료 메시지를 추가했습니다. 게임을 다시 실행하니 멈춤 현상이 거의 사라졌습니다.

"훨씬 부드러워졌어요!" 박게임 씨가 만족스러운 표정으로 말했습니다. "이제 사용자들이 불편함을 느끼지 않을 거예요." 타일맵 로딩을 최적화하면 게임의 첫인상이 크게 개선됩니다.

로딩 시간은 사용자 경험의 핵심 요소입니다. 여러분도 사전 로딩과 비동기 처리를 활용하여 부드러운 게임을 만들어 보세요.

실전 팁

💡 - 로딩 중에는 로딩 인디케이터나 프로그레스 바를 표시하면 사용자 경험이 개선됩니다

  • 개발 중에는 print 문으로 로딩 시간을 측정하여 병목 지점을 파악하세요

5. 충돌 레이어 설정

김게임 씨가 플레이어 캐릭터를 맵에 추가했습니다. 캐릭터가 자유롭게 움직이는데, 문제가 생겼습니다.

"벽을 통과해서 지나가요!" 박게임 씨가 웃으며 말했습니다. "충돌 레이어를 설정하지 않아서 그래요."

충돌 레이어는 타일맵에서 플레이어나 오브젝트가 지나갈 수 없는 영역을 정의하는 레이어입니다. 마치 투명한 벽을 세우는 것과 같습니다.

Tiled에서 객체 레이어로 충돌 영역을 그리고, Flame에서 이 정보를 읽어 물리 엔진과 연동하면 자연스러운 충돌 처리가 가능합니다.

다음 코드를 살펴봅시다.

class MyGame extends FlameGame with HasCollisionDetection {
  @override
  Future<void> onLoad() async {
    final tiledMap = await TiledComponent.load(
      'level1.tmx',
      Vector2.all(16),
    );
    add(tiledMap);

    // 충돌 레이어 찾기 (Tiled에서 'collision'이라는 이름의 객체 레이어)
    final collisionLayer = tiledMap.tileMap.getLayer<ObjectGroup>('collision');

    if (collisionLayer != null) {
      // 각 충돌 객체를 순회하며 처리
      for (final obj in collisionLayer.objects) {
        // 사각형 충돌 영역 생성
        add(RectangleHitbox(
          position: Vector2(obj.x, obj.y),
          size: Vector2(obj.width, obj.height),
        ));
      }
    }
  }
}

김게임 씨의 캐릭터가 벽을 유령처럼 통과했습니다. 게임의 기본 원리가 무너지는 순간이었습니다.

"플레이어가 벽에 막혀야 하는데 어떻게 하죠?" 박게임 씨가 Tiled 화면을 켰습니다. "충돌 레이어를 만들어야 해요.

보이지 않는 벽을 세우는 거죠." 충돌 레이어란 정확히 무엇일까요? 쉽게 비유하자면, 충돌 레이어는 마치 투명한 플라스틱 벽과 같습니다.

눈에는 보이지 않지만 물리적으로 존재하여 사람이 통과할 수 없습니다. 게임에서도 마찬가지입니다.

화면에는 보이지 않지만 캐릭터의 이동을 막는 보이지 않는 영역입니다. 왜 충돌 레이어가 필요할까요?

타일맵은 기본적으로 그림일 뿐입니다. 벽 타일이 그려져 있어도 게임 엔진은 그것이 벽인지 바닥인지 구분할 수 없습니다.

단지 이미지를 렌더링할 뿐입니다. 캐릭터가 벽을 통과하는 것을 막으려면 명시적으로 충돌 영역을 정의해야 합니다.

초창기에는 타일마다 충돌 속성을 일일이 설정했습니다. "이 타일은 충돌 가능, 저 타일은 충돌 불가" 이런 식으로 수백 개의 타일을 설정하는 것은 비효율적이었습니다.

또한 복잡한 형태의 충돌 영역을 표현하기 어려웠습니다. 바로 이런 문제를 해결하기 위해 객체 레이어 기반 충돌 시스템이 등장했습니다.

Tiled에서 객체 레이어를 만들고 충돌 영역을 그립니다. 사각형, 다각형, 타원 등 다양한 형태를 지원합니다.

벽이 있는 곳에 사각형을 그리고, 경사면은 다각형으로 그립니다. 마치 그림 그리듯이 직관적으로 작업할 수 있습니다.

객체 레이어의 이름은 보통 'collision'이나 'walls'로 짓습니다. 이름은 나중에 코드에서 찾을 때 사용됩니다.

일관된 명명 규칙을 사용하면 실수를 줄일 수 있습니다. 충돌 객체를 그릴 때는 정확성이 중요합니다.

타일과 정확히 일치하도록 그려야 합니다. 픽셀 하나라도 어긋나면 캐릭터가 벽에서 약간 떨어져 멈추거나, 벽에 살짝 파고들 수 있습니다.

Tiled의 스냅 기능을 활용하면 정확하게 그릴 수 있습니다. Flame에서는 이 충돌 객체를 읽어서 히트박스로 변환합니다.

getLayer 메서드로 충돌 레이어를 찾고, objects 속성으로 모든 충돌 객체를 가져옵니다. 각 객체의 위치와 크기 정보를 사용하여 RectangleHitbox를 생성합니다.

Flame의 충돌 감지 시스템을 사용하려면 FlameGame에 HasCollisionDetection을 믹스인해야 합니다. 이렇게 하면 게임 엔진이 자동으로 충돌을 감지하고 처리합니다.

캐릭터에도 히트박스를 추가하면 벽과 충돌했을 때 이동이 차단됩니다. 충돌 처리 로직은 간단합니다.

캐릭터가 이동하려고 할 때, 이동할 위치에 충돌 객체가 있는지 확인합니다. 충돌이 감지되면 이동을 취소하거나, 충돌 지점까지만 이동합니다.

Flame의 충돌 시스템이 이 과정을 자동으로 처리해줍니다. 성능을 고려하면 충돌 객체를 최소화하는 것이 좋습니다.

타일마다 개별 히트박스를 만들지 말고, 연속된 벽은 하나의 큰 사각형으로 합치세요. 충돌 객체가 적을수록 충돌 감지 연산이 빨라집니다.

김게임 씨는 Tiled로 돌아가 충돌 레이어를 만들었습니다. 모든 벽 타일 위에 사각형을 그렸습니다.

코드에서 이 정보를 읽어 히트박스를 생성했습니다. 게임을 실행하니 캐릭터가 벽에 딱 막혔습니다.

"완벽해요!" 박게임 씨가 엄지를 치켜세웠습니다. "이제 진짜 게임처럼 느껴지죠?" 충돌 레이어를 제대로 설정하면 게임의 물리적 법칙이 살아납니다.

플레이어는 벽을 넘을 수 없고, 함정에 빠질 수 있으며, 아이템과 상호작용할 수 있습니다. 여러분도 Tiled에서 충돌 레이어를 만들고 Flame에서 히트박스로 변환해 보세요.

실전 팁

💡 - 충돌 레이어는 화면에 표시되지 않으므로 Tiled에서 레이어를 눈에 띄는 색으로 설정하면 작업하기 편합니다

  • 복잡한 충돌 영역은 다각형 객체를 사용하면 더 정확하게 표현할 수 있습니다

6. 동적 타일 변경

김게임 씨가 게임에 퍼즐 요소를 추가하고 싶었습니다. "플레이어가 스위치를 누르면 문이 열리게 하고 싶어요." 박게임 씨가 고개를 끄덕였습니다.

"그럼 동적으로 타일을 변경해야 해요."

동적 타일 변경은 게임 실행 중에 타일맵의 타일을 프로그래밍 방식으로 교체하는 기법입니다. 마치 레고 블록을 빼고 다른 블록을 끼우는 것처럼, 특정 위치의 타일을 다른 타일로 바꿀 수 있습니다.

이를 통해 문이 열리고, 다리가 만들어지고, 함정이 작동하는 등 인터랙티브한 요소를 구현할 수 있습니다.

다음 코드를 살펴봅시다.

class InteractiveMap extends TiledComponent {
  InteractiveMap(super.map, super.tilesetProvider);

  // 특정 위치의 타일을 변경하는 메서드
  void changeTile(int layerIndex, int x, int y, int newTileId) {
    // 레이어 가져오기
    final layer = tileMap.map.layers[layerIndex] as TileLayer;

    // 타일 데이터 변경 (1D 배열 인덱스 계산)
    final index = y * tileMap.map.width + x;
    layer.data![index] = newTileId;

    // 맵 다시 렌더링 (변경사항 반영)
    refreshCache();
  }

  // 문 열기 예제
  void openDoor(int doorX, int doorY) {
    changeTile(0, doorX, doorY, 0); // 0 = 빈 타일
  }
}

김게임 씨는 게임에 퍼즐 요소를 추가하고 싶었습니다. 플레이어가 레버를 당기면 멀리 있는 문이 열리는 장치를 만들고 싶었습니다.

"이걸 어떻게 구현하죠?" 박게임 씨가 설명을 시작했습니다. "타일맵은 정적이지만, 우리가 동적으로 바꿀 수 있어요." 동적 타일 변경이란 정확히 무엇일까요?

쉽게 비유하자면, 동적 타일 변경은 마치 벽에 붙인 포스터를 떼고 다른 포스터를 붙이는 것과 같습니다. 벽 자체는 그대로지만, 표면의 그림이 바뀝니다.

타일맵도 마찬가지입니다. 맵 구조는 그대로 유지하면서 특정 위치의 타일만 다른 타일로 교체합니다.

왜 동적 타일 변경이 필요할까요? 정적인 맵만으로는 재미있는 게임을 만들기 어렵습니다.

플레이어와 상호작용하는 요소가 필요합니다. 스위치를 누르면 문이 열리고, 적을 처치하면 다리가 만들어지고, 시간이 지나면 함정이 작동합니다.

이런 요소들이 게임에 생동감을 불어넣습니다. 초창기에는 이런 요소를 별도의 게임 오브젝트로 만들었습니다.

문을 스프라이트로 만들고, 열림 상태와 닫힘 상태를 애니메이션으로 처리했습니다. 하지만 이 방식은 타일맵과 따로 관리해야 해서 복잡했습니다.

충돌 처리도 별도로 해야 했습니다. 바로 이런 문제를 해결하기 위해 동적 타일 변경 기법이 등장했습니다.

타일맵의 타일 데이터는 2차원 배열로 저장됩니다. 각 칸에는 타일 ID가 들어갑니다.

이 ID를 바꾸면 해당 위치의 타일이 바뀝니다. 예를 들어 (5, 3) 위치에 벽 타일(ID: 10)이 있다면, 이 값을 빈 타일(ID: 0)로 바꾸면 벽이 사라집니다.

flame_tiled에서는 TileLayer의 data 배열에 접근하여 값을 수정합니다. 2차원 좌표를 1차원 인덱스로 변환하는 공식은 간단합니다.

index = y × width + x 입니다. (2, 3) 위치를 찾으려면, 맵 너비가 10이라면 3 × 10 + 2 = 32번째 요소를 수정하면 됩니다.

타일 ID를 변경한 후에는 맵을 다시 렌더링해야 합니다. 그렇지 않으면 화면에 변경사항이 반영되지 않습니다.

refreshCache 메서드를 호출하면 Flame이 맵을 다시 그립니다. 이 과정은 성능에 영향을 줄 수 있으므로, 필요할 때만 호출해야 합니다.

실전에서는 타일 변경을 메서드로 캡슐화하는 것이 좋습니다. changeTile 메서드를 만들어 레이어 인덱스, 좌표, 새 타일 ID를 받도록 합니다.

이렇게 하면 코드 재사용성이 높아지고, 실수도 줄어듭니다. 문 열기는 전형적인 사용 예입니다.

문이 있는 위치를 찾아 타일 ID를 빈 타일(보통 0)로 바꿉니다. 필요하면 충돌 객체도 제거해야 합니다.

그렇지 않으면 문은 보이지 않지만 여전히 통과할 수 없는 상태가 됩니다. 반대로 타일을 추가할 수도 있습니다.

플레이어가 블록을 설치하는 게임이라면, 빈 공간에 블록 타일을 추가합니다. 마인크래프트처럼 플레이어가 세계를 변형할 수 있는 게임에서 유용합니다.

애니메이션 효과를 주려면 타일을 순차적으로 변경합니다. 문이 열릴 때 닫힌 문 타일 → 반쯤 열린 문 타일 → 완전히 열린 문 타일 순서로 바꾸면 부드러운 애니메이션처럼 보입니다.

타이머를 사용하여 일정 간격으로 타일을 변경하면 됩니다. 주의할 점은 레이어 구조를 이해하는 것입니다.

Tiled 맵은 여러 레이어로 구성됩니다. 배경 레이어, 오브젝트 레이어, 전경 레이어 등.

올바른 레이어의 타일을 변경해야 원하는 효과를 얻을 수 있습니다. 레이어 인덱스나 이름으로 정확히 지정하세요.

김게임 씨는 코드를 작성하여 스위치를 누르면 문이 열리도록 구현했습니다. 레버 오브젝트와 상호작용하면 changeTile 메서드가 호출되어 문 타일이 사라졌습니다.

"우와, 정말 작동해요!" 박게임 씨가 미소 지었습니다. "이제 여러분만의 창의적인 퍼즐을 만들 수 있을 거예요." 동적 타일 변경을 마스터하면 정적인 맵을 살아있는 세계로 바꿀 수 있습니다.

플레이어의 행동에 반응하는 환경은 게임의 몰입도를 크게 높입니다. 여러분도 동적 타일 변경을 활용하여 인터랙티브한 요소를 추가해 보세요.

실전 팁

💡 - 타일 변경 후 refreshCache 호출은 비용이 크므로, 여러 타일을 바꿀 때는 모두 변경한 후 한 번만 호출하세요

  • 변경된 타일 정보를 저장하면 세이브/로드 기능을 구현할 때 유용합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Flutter#Flame#TileMap#GameDevelopment#Tiled#Flutter,Flame,Game

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.