본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 29. · 3 Views
Flame 소개 및 환경 설정 완벽 가이드
Flutter 기반의 2D 게임 엔진 Flame의 기초부터 환경 설정까지 차근차근 알아봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실무 중심으로 설명합니다.
목차
1. Flame 게임 엔진이란?
어느 날 김개발 씨는 Flutter로 모바일 앱을 만들다가 문득 궁금해졌습니다. "Flutter로 게임도 만들 수 있을까?" 검색을 하다가 Flame이라는 이름을 발견했습니다.
같은 팀 선배인 박게임 씨에게 물어보니 환한 미소를 지으며 답했습니다.
Flame은 Flutter 위에서 동작하는 2D 게임 엔진입니다. 마치 Flutter가 모바일 앱을 쉽게 만들어주듯이, Flame은 2D 게임 개발을 쉽게 만들어줍니다.
Flutter의 강력한 크로스 플랫폼 능력을 그대로 활용하면서 게임에 필요한 기능들을 추가로 제공합니다.
다음 코드를 살펴봅시다.
// Flame의 기본 게임 클래스
import 'package:flame/game.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// 게임 초기화 로직
// 스프라이트, 사운드 등을 로드합니다
await super.onLoad();
}
@override
void update(double dt) {
// 매 프레임마다 호출됩니다
// dt는 이전 프레임 이후 경과 시간(초)입니다
super.update(dt);
}
}
김개발 씨는 입사 6개월 차 Flutter 개발자입니다. 그동안 여러 모바일 앱을 만들어왔지만, 게임 개발은 처음입니다.
회사에서 간단한 2D 게임 프로젝트가 시작되면서 김개발 씨도 게임 팀에 합류하게 되었습니다. 첫 날, 박게임 씨가 김개발 씨의 자리로 다가와 말했습니다.
"Flutter 할 줄 아시죠? 그럼 Flame도 금방 배울 수 있어요." 그렇다면 Flame이란 정확히 무엇일까요?
쉽게 비유하자면, Flame은 마치 요리사를 위한 전문 주방 도구 세트와 같습니다. 일반 가정용 주방 도구로도 요리는 할 수 있지만, 전문 요리사용 도구를 쓰면 훨씬 효율적이고 정교한 요리를 만들 수 있습니다.
Flutter라는 훌륭한 기본 도구가 있지만, Flame은 거기에 게임 개발에 특화된 도구들을 추가해줍니다. Flame이 없던 시절에는 어땠을까요?
Flutter로 게임을 만들려면 스프라이트 애니메이션, 충돌 감지, 게임 루프 같은 모든 것을 직접 구현해야 했습니다. 한두 개는 괜찮지만, 복잡한 게임이 되면 수백 줄의 반복적인 코드를 작성해야 했습니다.
더 큰 문제는 성능 최적화였습니다. 매 프레임마다 수십 개의 객체를 업데이트하려면 세밀한 최적화가 필요한데, 이를 매번 직접 구현하는 것은 비효율적이었습니다.
바로 이런 문제를 해결하기 위해 Flame이 등장했습니다. Flame을 사용하면 게임 오브젝트 관리가 자동으로 처리됩니다.
또한 물리 엔진, 타일맵, 파티클 시스템 같은 복잡한 기능도 간단한 API로 사용할 수 있습니다. 무엇보다 Flutter의 크로스 플랫폼 능력을 그대로 활용할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 FlameGame을 상속받는 것을 볼 수 있습니다.
이것이 Flame 게임의 핵심입니다. onLoad 메서드는 게임이 시작될 때 한 번만 호출되어 초기화를 담당합니다.
update 메서드는 매 프레임마다 호출되며, dt 파라미터를 통해 시간에 따른 움직임을 구현할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 교육용 퍼즐 게임을 개발한다고 가정해봅시다. iOS와 Android 모두 지원해야 하고, 나중에는 웹 버전도 출시할 계획입니다.
Flame을 사용하면 하나의 코드베이스로 모든 플랫폼을 커버할 수 있습니다. 실제로 많은 인디 게임 개발사와 교육 콘텐츠 회사에서 이런 패턴을 적극 활용하고 있습니다.
하지만 주의할 점도 있습니다. Flame은 2D 게임에 특화되어 있습니다.
3D 게임을 만들려면 Unity나 Unreal Engine 같은 다른 엔진을 고려해야 합니다. 또한 초보자들이 흔히 하는 실수 중 하나는 모든 로직을 update 메서드에 넣는 것입니다.
이렇게 하면 코드가 복잡해지고 유지보수가 어려워집니다. 따라서 컴포넌트 시스템을 활용하여 기능을 분리해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박게임 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, Flutter 위에서 돌아가는 게임 전용 프레임워크구나!" Flame을 제대로 이해하면 Flutter 개발 경험을 게임 개발에도 활용할 수 있습니다. 여러분도 오늘 배운 내용을 바탕으로 간단한 게임부터 만들어보세요.
실전 팁
💡 - Flame은 Flutter 패키지이므로 Flutter를 먼저 익히는 것이 중요합니다
- 공식 문서(docs.flame-engine.org)에 풍부한 예제가 있으니 참고하세요
- 처음에는 간단한 게임(핑퐁, 벽돌깨기)부터 시작하는 것을 추천합니다
2. 2D 게임 개발 기초 이해
김개발 씨가 박게임 씨에게 물었습니다. "게임 개발이 일반 앱 개발과 뭐가 다른가요?" 박게임 씨는 화이트보드를 꺼내며 설명을 시작했습니다.
"가장 큰 차이는 바로 게임 루프입니다."
게임 루프는 게임이 실행되는 동안 계속 반복되는 핵심 사이클입니다. 일반 앱은 사용자 입력이 있을 때만 반응하지만, 게임은 매 프레임마다 화면을 갱신합니다.
보통 초당 60번, 즉 60 FPS로 동작하며, 각 루프마다 입력 처리, 게임 로직 업데이트, 화면 렌더링이 순차적으로 일어납니다.
다음 코드를 살펴봅시다.
// 게임 루프의 핵심 구조
class GameComponent extends Component {
Vector2 position = Vector2(100, 100);
double speed = 200; // 초당 200픽셀 이동
@override
void update(double dt) {
// dt: Delta Time (이전 프레임 이후 경과 시간)
// 60 FPS라면 dt는 약 0.016초 (1/60)
// 시간 기반 이동: 속도 * 시간
position.x += speed * dt;
// 화면 밖으로 나가면 처음으로
if (position.x > 800) position.x = 0;
}
}
김개발 씨는 지금까지 버튼을 누르면 화면이 바뀌는 일반적인 앱만 만들어왔습니다. 할 일 관리 앱, 뉴스 리더, 채팅 앱 등 모두 이벤트 기반으로 동작했습니다.
사용자가 뭔가를 하기 전까지는 화면이 가만히 있었죠. 그런데 게임은 완전히 다릅니다.
아무것도 안 해도 캐릭터가 움직이고, 배경이 스크롤되고, 적이 공격해옵니다. 그렇다면 게임 루프란 정확히 무엇일까요?
쉽게 비유하자면, 게임 루프는 마치 심장의 박동과 같습니다. 심장은 쉬지 않고 계속 뛰면서 혈액을 순환시킵니다.
게임 루프도 쉬지 않고 계속 돌면서 게임 세계를 살아있게 만듭니다. 보통 1초에 60번 뛰는데, 이것을 **60 FPS(Frames Per Second)**라고 부릅니다.
게임 루프가 없던 시절에는 어땠을까요? 초창기 게임들은 고정된 타이밍으로 동작했습니다.
예를 들어 "10밀리초마다 캐릭터를 오른쪽으로 5픽셀 이동"처럼 말이죠. 그런데 이렇게 하면 큰 문제가 생깁니다.
빠른 컴퓨터에서는 게임이 빨라지고, 느린 컴퓨터에서는 게임이 느려집니다. 같은 게임인데 기기마다 난이도가 달라지는 황당한 상황이 벌어졌죠.
바로 이런 문제를 해결하기 위해 델타 타임(Delta Time) 개념이 등장했습니다. 델타 타임을 사용하면 프레임 속도와 무관하게 일정한 속도로 게임이 동작합니다.
또한 시간 기반 계산이 가능해져서 물리 법칙을 정확하게 구현할 수 있습니다. 무엇보다 다양한 기기에서 동일한 게임 경험을 제공할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. speed 변수는 초당 이동 거리를 나타냅니다.
200이라는 것은 1초에 200픽셀 이동한다는 뜻입니다. update 메서드는 매 프레임마다 호출되는데, dt 파라미터가 핵심입니다.
60 FPS라면 dt는 약 0.016초입니다. position.x += speed * dt가 바로 시간 기반 이동의 핵심입니다.
속도에 시간을 곱하면 이동 거리가 나옵니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 러닝 게임을 개발한다고 가정해봅시다. 캐릭터가 계속 앞으로 달려가고, 장애물이 다가옵니다.
델타 타임을 사용하면 최신 스마트폰이든 구형 기기든 동일한 속도로 게임이 진행됩니다. 플레이어 A가 3초 만에 장애물 10개를 넘었다면, 플레이어 B도 기기와 상관없이 3초 만에 똑같은 장애물 10개를 만나게 됩니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 고정된 값을 더하는 것입니다.
position.x += 5 이런 식으로 작성하면 프레임 속도에 따라 이동 속도가 달라집니다. 이렇게 하면 테스트할 때는 괜찮아 보이지만, 다른 기기에서 게임이 너무 빠르거나 느려질 수 있습니다.
따라서 반드시 dt를 곱해서 시간 기반으로 계산해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박게임 씨의 설명을 들은 김개발 씨는 놀랐습니다. "아, 그래서 게임은 계속 돌아가는구나!" 게임 루프와 델타 타임을 제대로 이해하면 모든 기기에서 일관된 게임을 만들 수 있습니다.
여러분도 오늘 배운 내용을 바탕으로 움직이는 캐릭터를 만들어보세요.
실전 팁
💡 - 항상 dt를 곱해서 시간 기반 계산을 하세요
- 60 FPS 기준으로 dt는 약 0.016초입니다
- 복잡한 계산은 매 프레임마다 하지 말고 필요할 때만 하세요
3. Flutter 프로젝트에 Flame 추가
이제 본격적으로 시작할 시간입니다. 김개발 씨가 새 프로젝트를 만들려고 하자, 박게임 씨가 손을 저었습니다.
"잠깐! Flutter 프로젝트에 Flame을 추가하는 방법부터 배워야죠."
Flame 추가는 다른 Flutter 패키지를 추가하는 것과 동일합니다. pubspec.yaml 파일에 의존성을 추가하고 flutter pub get을 실행하면 됩니다.
Flame은 모듈식 구조로 되어 있어서 필요한 기능만 선택적으로 추가할 수 있습니다.
다음 코드를 살펴봅시다.
# pubspec.yaml 파일
name: my_flame_game
description: 나의 첫 Flame 게임
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
flame: ^1.17.0 # Flame 기본 패키지
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
김개발 씨는 이미 Flutter 프로젝트를 여러 번 만들어봤습니다. VS Code를 열고 터미널을 띄우는 것도 익숙합니다.
하지만 Flame을 추가하는 것은 처음이라 조금 긴장됩니다. 박게임 씨가 김개발 씨의 화면을 보며 말했습니다.
"걱정 마세요. 일반 패키지 추가하는 것과 똑같아요." 그렇다면 Flame 패키지 추가는 정확히 어떻게 할까요?
쉽게 비유하자면, 패키지 추가는 마치 레고 블록을 조립하는 것과 같습니다. 기본 Flutter라는 베이스 판이 있고, 거기에 Flame이라는 특별한 블록을 끼워 넣는 것입니다.
한 번만 추가하면 프로젝트 전체에서 Flame의 모든 기능을 사용할 수 있습니다. 패키지 관리 시스템이 없던 시절에는 어땠을까요?
예전에는 라이브러리를 사용하려면 직접 파일을 다운로드해서 프로젝트에 복사해야 했습니다. 버전이 업데이트되면 또 다시 다운로드하고 복사하는 과정을 반복했죠.
더 큰 문제는 의존성 충돌이었습니다. A 라이브러리가 B 라이브러리 버전 1.0을 요구하는데, C 라이브러리가 B 버전 2.0을 요구하면 손수 해결해야 했습니다.
바로 이런 문제를 해결하기 위해 pubspec.yaml 기반의 의존성 관리가 등장했습니다. pubspec.yaml을 사용하면 선언적으로 의존성을 관리할 수 있습니다.
또한 자동으로 버전을 관리해주고, 의존성 충돌도 자동으로 해결해줍니다. 무엇보다 팀원 모두가 동일한 버전을 사용하게 되어 "제 컴퓨터에서는 되는데요" 같은 문제가 사라집니다.
위의 코드를 한 줄씩 살펴보겠습니다. environment 섹션은 이 프로젝트가 요구하는 Dart SDK 버전을 명시합니다.
dependencies는 실제 앱에 포함될 패키지들입니다. flame: ^1.17.0이 핵심인데, 캐럿(^) 기호는 1.17.0 이상 2.0.0 미만의 버전을 자동으로 사용한다는 뜻입니다.
dev_dependencies는 개발할 때만 필요한 패키지들입니다. 실제 현업에서는 어떻게 활용할까요?
팀 프로젝트를 진행한다고 가정해봅시다. 김개발 씨가 Flame 1.17.0으로 개발하고, 이박사 씨가 나중에 프로젝트를 받아서 flutter pub get을 실행하면 정확히 같은 버전이 설치됩니다.
이것이 pubspec.lock 파일 덕분입니다. 이 파일은 실제로 설치된 정확한 버전을 기록하여 재현 가능한 빌드를 보장합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 패키지를 추가하는 것입니다.
패키지가 많아질수록 앱 크기가 커지고 빌드 시간이 길어집니다. 이렇게 하면 유지보수도 어려워집니다.
따라서 정말 필요한 패키지만 추가하고, 간단한 기능은 직접 구현하는 것이 좋습니다. 또 다른 실수는 버전을 고정하지 않는 것입니다.
flame: any 같은 식으로 작성하면 나중에 큰 업데이트가 나왔을 때 호환성 문제가 발생할 수 있습니다. 항상 명시적인 버전 범위를 지정하세요.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박게임 씨의 안내에 따라 pubspec.yaml을 수정한 김개발 씨는 터미널에서 명령어를 입력했습니다.
flutter pub get 몇 초 후 "Got dependencies!" 메시지가 나타났습니다. 김개발 씨가 환하게 웃으며 말했습니다.
"이제 Flame을 쓸 수 있겠네요!" Flame 패키지 추가는 간단하지만 모든 게임 개발의 시작점입니다. 여러분도 오늘 배운 대로 프로젝트에 Flame을 추가해보세요.
실전 팁
💡 - 터미널에서 flutter pub add flame 명령어로도 추가할 수 있습니다
- pubspec.lock 파일은 Git에 꼭 포함시키세요
- 패키지 업데이트는
flutter pub upgrade명령어를 사용하세요
4. 개발 환경 설정
Flame 패키지를 추가했지만, 그것만으로는 부족합니다. 김개발 씨가 코드를 작성하려는데, 박게임 씨가 말했습니다.
"먼저 main.dart 파일을 수정해야 해요. 게임은 일반 앱과 초기화 방식이 다르거든요."
개발 환경 설정은 main.dart 파일에서 Flame 게임을 실행할 수 있도록 준비하는 과정입니다. 일반 Flutter 앱은 runApp에 위젯을 전달하지만, Flame 게임은 GameWidget을 사용하여 게임 인스턴스를 실행합니다.
전체 화면 모드와 방향 고정 등의 설정도 함께 진행합니다.
다음 코드를 살펴봅시다.
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flame/game.dart';
import 'my_game.dart';
void main() {
// Flutter 바인딩 초기화
WidgetsFlutterBinding.ensureInitialized();
// 화면 방향을 가로 모드로 고정
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
// 상태바와 네비게이션바 숨기기 (전체 화면)
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
runApp(GameWidget(game: MyGame()));
}
김개발 씨는 지금까지 수많은 Flutter 앱의 main.dart를 작성해왔습니다. 대부분 MaterialApp으로 시작해서 홈 화면 위젯을 지정하는 패턴이었죠.
그런데 게임은 다르다고 합니다. 박게임 씨가 설명을 시작했습니다.
"일반 앱은 버튼, 텍스트, 리스트 같은 위젯들의 조합이에요. 하지만 게임은 전체 화면을 캔버스처럼 사용하죠." 그렇다면 게임 환경 설정은 정확히 무엇을 하는 걸까요?
쉽게 비유하자면, 게임 환경 설정은 마치 연극 무대를 준비하는 것과 같습니다. 관객석의 불을 끄고, 무대 조명을 켜고, 배우들이 등장할 수 있도록 준비하는 과정입니다.
게임도 마찬가지로 상태바를 숨기고, 화면 방향을 고정하고, 게임 엔진을 시작시켜야 합니다. 제대로 된 설정 없이 게임을 실행하면 어떻게 될까요?
화면을 돌리면 게임이 다시 시작되거나 레이아웃이 깨질 수 있습니다. 상태바와 네비게이션바가 게임 화면을 가려서 플레이 영역이 줄어들 수도 있습니다.
더 큰 문제는 터치 좌표 계산이 어긋나는 것입니다. 사용자가 화면 오른쪽을 터치했는데 게임에서는 중간으로 인식되는 황당한 상황이 벌어질 수 있습니다.
바로 이런 문제를 해결하기 위해 명시적인 환경 설정이 필요합니다. 화면 방향을 고정하면 일관된 게임 경험을 제공할 수 있습니다.
또한 전체 화면 모드를 활성화하면 최대 플레이 영역을 확보할 수 있습니다. 무엇보다 GameWidget을 사용하면 Flame의 최적화된 렌더링 엔진을 활용할 수 있다는 큰 이점이 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. **WidgetsFlutterBinding.ensureInitialized()**는 Flutter 엔진을 명시적으로 초기화합니다.
이것이 없으면 다음 설정들이 오류를 일으킬 수 있습니다. setPreferredOrientations는 화면 방향을 가로 모드로 고정합니다.
대부분의 2D 게임은 가로 모드가 적합합니다. setEnabledSystemUIMode는 상태바와 네비게이션바를 숨겨서 진정한 전체 화면을 만듭니다.
마지막으로 GameWidget이 게임 인스턴스를 받아서 실행합니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 세로 모드 퍼즐 게임을 개발한다면 DeviceOrientation.portraitUp만 허용하면 됩니다. 양방향 모두 지원하는 게임이라면 방향 고정을 하지 않고, 대신 화면 크기에 따라 레이아웃을 조정하는 반응형 디자인을 구현합니다.
어린이용 교육 게임이라면 실수로 뒤로가기를 누르는 것을 방지하기 위해 추가 설정을 할 수도 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 WidgetsFlutterBinding.ensureInitialized()를 빼먹는 것입니다. 이렇게 하면 시스템 설정을 변경할 때 오류가 발생합니다.
또 다른 실수는 너무 많은 방향을 허용하는 것입니다. 세로와 가로 모두 지원하면 UI를 두 번 디자인해야 하므로 개발 시간이 두 배로 늘어납니다.
따라서 한 가지 방향을 선택하고 집중하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
박게임 씨의 설명을 들으며 코드를 작성한 김개발 씨가 물었습니다. "my_game.dart 파일은 아직 안 만들었는데요?" 박게임 씨가 웃으며 답했습니다.
"그건 다음 단계에서 만들 거예요. 지금은 준비 단계니까요." 개발 환경을 제대로 설정하면 이후의 모든 작업이 순조롭게 진행됩니다.
여러분도 오늘 배운 대로 main.dart를 설정해보세요.
실전 팁
💡 - 가로 게임인지 세로 게임인지 먼저 결정하세요
- SystemUiMode.immersiveSticky는 사용자가 화면을 스와이프하면 일시적으로 상태바가 나타납니다
- 개발 중에는 전체 화면 모드를 끄고 테스트하는 것이 편리합니다
5. 첫 번째 Flame 앱 실행
드디어 첫 게임을 실행할 시간입니다. 김개발 씨는 떨리는 마음으로 키보드에 손을 올렸습니다.
박게임 씨가 옆에서 격려했습니다. "아주 간단한 게임부터 시작해요.
화면에 원 하나만 그려봅시다."
첫 번째 Flame 앱은 최소한의 코드로 게임이 실행되는 것을 확인하는 단계입니다. FlameGame 클래스를 상속받아서 기본 게임 클래스를 만들고, onLoad 메서드에서 초기화를 수행합니다.
간단한 CircleComponent를 화면에 그려서 Flame이 제대로 동작하는지 확인합니다.
다음 코드를 살펴봅시다.
// lib/my_game.dart
import 'package:flame/game.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
await super.onLoad();
// 화면 중앙에 파란색 원 생성
final circle = CircleComponent(
radius: 50,
position: size / 2, // 화면 중앙
anchor: Anchor.center, // 원의 중심을 기준점으로
paint: Paint()..color = Colors.blue,
);
add(circle); // 게임 월드에 추가
}
}
김개발 씨의 손가락이 키보드 위에서 빠르게 움직입니다. 지금까지 배운 내용을 종합하여 드디어 실제 코드를 작성하는 순간입니다.
머릿속으로 지금까지 박게임 씨가 설명한 내용을 복기합니다. 코드를 다 작성하고 나서 김개발 씨는 터미널에서 flutter run을 입력했습니다.
빌드가 시작되고, 몇 초 후 화면에 파란색 원이 나타났습니다. 그렇다면 이 코드는 정확히 어떻게 동작하는 걸까요?
쉽게 비유하자면, 게임 실행은 마치 무대 연극과 같습니다. MyGame 클래스는 연출가이고, onLoad는 공연 시작 전 준비 시간입니다.
CircleComponent는 무대에 등장하는 배우입니다. add 메서드를 호출하는 것은 배우를 무대에 올리는 것과 같습니다.
컴포넌트 시스템이 없던 시절에는 어떻게 했을까요? 모든 것을 직접 그려야 했습니다.
Canvas API를 사용하여 원을 그리는 코드를 작성하고, 위치를 계산하고, 매 프레임마다 다시 그려야 했습니다. 열 개의 원을 그리려면?
같은 코드를 열 번 반복해야 했죠. 더 큰 문제는 객체 관리였습니다.
원이 사라져야 할 때, 이동해야 할 때 모든 로직을 수동으로 처리해야 했습니다. 바로 이런 문제를 해결하기 위해 컴포넌트 시스템이 등장했습니다.
컴포넌트를 사용하면 재사용 가능한 게임 객체를 쉽게 만들 수 있습니다. 또한 add와 remove만으로 간단하게 객체를 관리할 수 있습니다.
무엇보다 각 컴포넌트가 독립적으로 동작하여 코드가 깔끔해진다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
FlameGame을 상속받으면 게임의 모든 기본 기능을 사용할 수 있습니다. onLoad는 비동기 메서드로, 게임이 시작될 때 한 번만 호출됩니다.
CircleComponent는 Flame이 제공하는 기본 도형 컴포넌트입니다. size / 2는 화면 크기를 2로 나눈 것, 즉 화면 중앙 좌표입니다.
Anchor.center는 원의 중심점을 기준으로 위치를 지정한다는 뜻입니다. add 메서드가 이 원을 게임 월드에 추가합니다.
실제 현업에서는 어떻게 활용할까요? 이 간단한 원이 게임 개발의 시작점입니다.
이 원에 움직임을 추가하면 캐릭터가 되고, 터치 입력을 받으면 버튼이 됩니다. 여러 개의 원을 만들면 파티클 이펙트가 됩니다.
실제로 많은 2D 게임이 이런 기본 도형들의 조합으로 시작합니다. 나중에 도형을 스프라이트 이미지로 교체하면 멋진 게임이 완성됩니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 onLoad에서 super.onLoad()를 호출하지 않는 것입니다.
이렇게 하면 게임이 제대로 초기화되지 않아 이상한 버그가 발생할 수 있습니다. 또 다른 실수는 Anchor를 설정하지 않는 것입니다.
기본값은 topLeft인데, 이러면 원의 왼쪽 위 모서리가 중앙에 오게 되어 원이 오른쪽 아래로 치우쳐 보입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
화면에 나타난 파란색 원을 보며 김개발 씨는 환호성을 질렀습니다. "됐다!" 박게임 씨가 미소를 지으며 말했습니다.
"축하합니다. 첫 Flame 게임을 만드셨네요.
비록 원 하나지만, 이것이 시작입니다." 첫 번째 게임을 성공적으로 실행하면 자신감이 생깁니다. 여러분도 오늘 배운 대로 원을 화면에 그려보세요.
그리고 색상을 바꾸거나 크기를 조절해보며 실험해보세요.
실전 팁
💡 - onLoad는 async 메서드이므로 await을 사용하여 이미지나 사운드를 로드할 수 있습니다
- 여러 개의 원을 만들려면 add를 여러 번 호출하면 됩니다
- Hot Reload가 작동하므로 코드를 수정하면 즉시 결과를 볼 수 있습니다
6. Flame 프로젝트 구조
파란색 원이 성공적으로 나타나자, 김개발 씨는 더 복잡한 게임을 만들고 싶어졌습니다. 박게임 씨가 김개발 씨의 열정을 눈치채고 말했습니다.
"그 전에 프로젝트 구조부터 제대로 잡아야 해요. 나중에 코드가 엉망이 되면 고치기 힘들거든요."
Flame 프로젝트 구조는 게임 코드를 체계적으로 관리하기 위한 폴더와 파일 구조입니다. 일반적으로 components, screens, managers 폴더로 나누어 각 역할별로 코드를 분리합니다.
이렇게 하면 프로젝트가 커져도 코드를 쉽게 찾고 수정할 수 있습니다.
다음 코드를 살펴봅시다.
// 권장 프로젝트 구조
lib/
main.dart // 앱 진입점
my_game.dart // 메인 게임 클래스
components/ // 게임 오브젝트들
player.dart // 플레이어 컴포넌트
enemy.dart // 적 컴포넌트
bullet.dart // 총알 컴포넌트
screens/ // 게임 화면들
main_menu.dart // 메인 메뉴
game_screen.dart // 게임 플레이 화면
game_over.dart // 게임 오버 화면
managers/ // 관리자 클래스들
audio_manager.dart // 사운드 관리
score_manager.dart // 점수 관리
김개발 씨는 그동안 작은 프로젝트만 해왔기 때문에 파일이 많아도 10개 정도였습니다. 하지만 박게임 씨가 보여준 회사의 게임 프로젝트는 파일이 수백 개나 되었습니다.
체계적인 구조 없이는 도저히 관리할 수 없을 것 같았습니다. 박게임 씨가 설명을 시작했습니다.
"처음부터 구조를 잘 잡아두면, 나중에 시간을 엄청나게 절약할 수 있어요." 그렇다면 좋은 프로젝트 구조란 정확히 무엇일까요? 쉽게 비유하자면, 프로젝트 구조는 마치 옷장 정리와 같습니다.
셔츠는 셔츠끼리, 바지는 바지끼리 모아두면 필요한 옷을 빠르게 찾을 수 있습니다. 게임 코드도 마찬가지로 컴포넌트는 컴포넌트끼리, 화면은 화면끼리 모아두면 원하는 코드를 즉시 찾을 수 있습니다.
구조 없이 개발하면 어떻게 될까요? 모든 파일이 lib 폴더에 섞여 있으면 찾고 싶은 파일을 찾기 어렵습니다.
player.dart를 수정하려는데 비슷한 이름의 파일이 여러 개 있어서 헷갈립니다. 더 큰 문제는 팀 협업입니다.
김개발 씨가 enemy.dart를 작업하고, 이박사 씨가 player.dart를 작업하는데, 두 파일이 한 폴더에 섞여 있으면 Git 충돌이 자주 발생합니다. 바로 이런 문제를 해결하기 위해 명확한 폴더 구조가 필요합니다.
폴더를 나누면 코드의 역할이 명확해집니다. 또한 파일을 빠르게 찾을 수 있고, 협업 시 충돌이 줄어듭니다.
무엇보다 새로운 팀원이 프로젝트를 이해하기 쉽다는 큰 이점이 있습니다. 위의 구조를 하나씩 살펴보겠습니다.
main.dart는 앱의 시작점입니다. 여기서 게임을 실행합니다.
my_game.dart는 메인 게임 클래스로, 전체 게임을 총괄합니다. components 폴더에는 플레이어, 적, 총알 같은 게임 오브젝트들이 들어갑니다.
screens 폴더에는 메뉴, 게임 화면, 게임 오버 화면 같은 화면 전환과 관련된 코드가 들어갑니다. managers 폴더에는 사운드, 점수, 데이터 저장 같은 게임 시스템을 관리하는 클래스들이 들어갑니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 슈팅 게임을 개발한다고 가정해봅시다.
플레이어가 "적이 너무 약해요"라고 피드백을 주면, components/enemy.dart 파일만 열어서 수정하면 됩니다. 배경 음악을 바꾸고 싶다면 managers/audio_manager.dart만 수정하면 됩니다.
이렇게 관심사의 분리가 명확하면 유지보수가 훨씬 쉬워집니다. 더 큰 프로젝트에서는 폴더를 더 세분화할 수도 있습니다.
components 안에 weapons, powerups 같은 하위 폴더를 만들 수 있습니다. screens 안에 ui 폴더를 만들어 버튼, 체력바 같은 UI 컴포넌트를 따로 관리할 수도 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 폴더를 너무 많이 만드는 것입니다.
파일이 10개밖에 없는데 폴더가 20개면 오히려 복잡해집니다. 이렇게 하면 간단한 수정을 할 때도 여러 폴더를 왔다 갔다 해야 합니다.
따라서 프로젝트 크기에 맞는 적절한 구조를 선택해야 합니다. 또 다른 실수는 일관성 없는 명명 규칙입니다.
어떤 파일은 player_component.dart인데 다른 파일은 enemy.dart면 혼란스럽습니다. 팀 전체가 동일한 규칙을 따라야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박게임 씨의 설명을 들은 김개발 씨가 고개를 끄덕였습니다.
"아, 그래서 회사 프로젝트가 그렇게 정리되어 있었구나!" 박게임 씨가 웃으며 답했습니다. "처음엔 귀찮아 보여도, 나중에 정말 고마워할 거예요." 프로젝트 구조를 제대로 잡으면 장기적으로 개발 속도가 빨라집니다.
여러분도 오늘 배운 구조를 바탕으로 프로젝트를 정리해보세요. 미래의 여러분이 고마워할 것입니다.
실전 팁
💡 - 작은 프로젝트라면 components와 screens 폴더만으로도 충분합니다
- utils나 helpers 폴더를 추가하여 공통 함수들을 관리할 수 있습니다
- assets 폴더는 이미지, 사운드 파일을 저장하는 곳입니다 (pubspec.yaml에 등록 필요)
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
타일맵 시스템 완벽 가이드
Flame 게임 엔진에서 타일맵을 활용하여 효율적으로 게임 월드를 구성하는 방법을 배웁니다. Tiled 에디터부터 충돌 처리, 동적 타일 변경까지 실무에서 바로 활용할 수 있는 내용을 다룹니다.
게임 루프와 컴포넌트 완벽 가이드
Flame 게임 엔진의 핵심인 게임 루프와 컴포넌트 시스템을 이해하고, 실전에서 활용하는 방법을 배웁니다. 초급 개발자도 쉽게 따라할 수 있도록 실무 예제와 함께 설명합니다.
Flutter/Flame 커스텀 렌더링 파이프라인 완벽 가이드
게임 엔진의 핵심인 렌더링 파이프라인을 직접 커스터마이징하는 방법을 배웁니다. Canvas부터 셰이더, 배치 렌더링까지 실무에서 바로 쓸 수 있는 최적화 기법을 소개합니다.
Flame 고급 컴포넌트 패턴 완벽 가이드
Flutter의 Flame 엔진으로 게임을 만들 때 필수적인 고급 컴포넌트 패턴을 배워봅니다. 상속, Mixin, HasGameRef 등 실무에서 자주 쓰이는 패턴을 초급자도 이해할 수 있도록 쉽게 설명합니다.
Riverpod 3.0 쇼핑 앱 종합 프로젝트 완벽 가이드
Flutter와 Riverpod 3.0을 활용한 실무 수준의 쇼핑 앱 개발 과정을 단계별로 학습합니다. 상품 목록, 장바구니, 주문, 인증, 검색 기능까지 모든 핵심 기능을 구현하며 상태 관리의 실전 노하우를 익힙니다.