Flutter로 게임을 개발해보자
이번 학기의 모바일 스터디는 flutter를 활용하여 색다른 프로젝트를 해보면서 다양한 인사이트를 얻는 것을 목표로 삼았습니다. 이에 따라 기존 앱 개발, 응용 프로그램 제작 등이 아닌 게임을 개발하는 것을 목표로 삼았습니다. Flame Framework 를 사용하여 게임을 개발해보면서 Dart 및 Flutter의 활용 범위를 넓히고, 타 엔진들 과의 구조 비교를 해보면서 다른 계열의 서비스를 개발할 때 적용할 수 있는 아이디어를 축적하는 것에 초점을 두고 프로젝트를 진행할 예정입니다.
Flame이란 무엇인가
Flame은 Flutter에서 2D 게임을 개발할 수 있게 해주는 경량화된 게임 엔진입니다. 간단한 구조로, Flutter의 기존 위젯들과 원활하게 통합되며, 애니메이션, 물리 엔진, 충돌 감지 등의 기능을 제공합니다. Flame은 빠르게 게임을 개발할 수 있도록 다양한 도구와 유틸리티를 제공하며, Flutter 생태계를 활용하여 다양한 플랫폼에서 게임을 배포할 수 있다는 장점이 있습니다.
Flame의 장점
- Flutter 통합: Flutter 위젯과 함께 사용 가능하여 UI와 게임 로직을 쉽게 결합할 수 있음.
- 다양한 플랫폼 지원: Flutter 기반이므로 Android, iOS, 웹, 데스크톱 등 여러 플랫폼에 게임을 배포 가능.
- 경량 엔진: 작은 프로젝트에 적합한 경량화된 게임 엔진으로, 빠른 개발이 가능.
- 풍부한 기능 제공: 애니메이션, 물리 엔진, 충돌 감지 등 2D 게임 개발에 필요한 다양한 유틸리티 지원.
타 엔진과의 비교
특징 | Unreal Engine | Unity | Flame |
주 용도 | 고사양 3D 게임, 영화 제작, VR/AR 콘텐츠 | 2D/3D 게임, AR/VR, 모바일 및 데스크톱 앱 개발 | 2D 게임, 모바일 및 웹 게임 개발 |
언어 | C++ (Blueprint로 시각적 스크립팅 지원) | C# | Dart |
그래픽 성능 | 매우 높은 그래픽 품질, 리얼타임 렌더링 지원 | 높은 그래픽 품질 제공, 경량화된 3D 엔진 | 경량 2D 그래픽에 최적화 |
사용 난이도 | 높은 진입장벽 (복잡한 기능과 고급 설정) | 초보자부터 전문가까지 다양한 난이도로 접근 가능 | Flutter와 유사한 사용 방식, 간단한 구조 |
플랫폼 지원 | 콘솔, PC, 모바일, VR/AR, 웹 등 | 콘솔, PC, 모바일, VR/AR, 웹 등 | 모바일(Android, iOS), 웹, 데스크톱 (Flutter 기반) |
커뮤니티 | 대형 커뮤니티와 많은 학습 자료 | 대규모 커뮤니티 및 풍부한 학습 리소스 | Flutter 기반 커뮤니티 활용 가능 |
라이선스 비용 | 로열티 기반 (수익이 일정 수준 이상 시 로열티 발생) | 무료/유료 (특정 수익 이상 시 비용 발생) | 무료 (오픈소스) |
확장성 | AAA급 게임 제작에 적합한 확장성 제공 | Plug-in 및 Asset store로 확장성 뛰어남 | Flutter 패키지를 활용한 간단한 확장성 |
2D/3D 지원 | 2D와 3D 모두 지원 (주로 3D에 최적화) | 2D와 3D 모두 균형 있게 지원 | 2D 전용 |
Flame 개요
Components
모든 Components 는
Component
를 상속받습니다. 또한 다른 Components 를 자식 클래스로 가질 수 있습니다. 해당 시스템을 Flame Component System 또는 FCS 라고 부릅니다.Component Lifecycle
onLoad
onLoad
는 component 의 비동기 초기화시 실행하는 method 입니다.- 해당 method는 component 의 life cycle 중 한 번만 실행되므로 asynchronous constructor로도 여길 수 있습니다.
onGameResize
onGameResize
는 화면 크기 변경 시마다 호출됩니다.- 또한, component 가 component tree에 추가될 때 또한 호출됩니다.
onParentResize
onParentResize
는onGameResize
와 비슷하게, component가 component tree에 추가될 때 호출됩니다.- 또한, parent component의 크기 변경 시마다 호출됩니다.
onMount
onMount
는 component가 Game Tree에 추가될 때마다 실행됩니다.- 해당 method 내에서
late final
변수를 초기화하는 것은 권장하지 않습니다. - parent component 가
mount
된 상태여야 해당 method 가 호출되며, 조건 미충족 시 parent component가mount
될 때까지 기다립니다.
onRemove
onRemove
는 component가 Game Tree 제거되기 전에 실행할 code를 재정의합니다.- 해당 method는 component가 부모의
remove
를 통해 제거되든, component의remove
메서드를 통해 제거되든 상관없이 한 번만 실행됩니다.
onChildChanged
onChildChanged
는 부모의 child component의 변경을 감지해야 할 때 실행할 code를 재정의합니다.- 해당 method는 자식이 부모에 추가되거나 제거될 때마다 호출되며, 자식이 부모를 변경할 때도 호출됩니다.
- parameter는 target child compnent와 해당 자식이 겪은 변화(추가 또는 제거)가 포함됩니다.
PositionComponent
PositionComponent
는 화면에 위치하는 object를 의미하며 위치와 크기를 가지는 component는 모두 이에 해당합니다.(e.g. 움직이는 사각형, 회전하는 이미지) 때에 따라 PositionComponet
들의 Group 또한 PositionComponent
입니다. 기본
PosditionComponent
의 경우 다음과 같은 변수를 가집니다.- Position
- Vector2 형식으로 component 의
anchor
의 부모에 따른 상대 좌표를 나타냅니다. - 만약 부모가
FlameGame
인 경우 viewport 기준의 좌표를 나타냅니다.
- Size
- camerad의 zoom level 이 1.0일 때 기준의 크기입니다.
- 해당 값은 부모의 영향을 받지 않습니다.
- Scale
- Vector2 형식으로 해당 component 및 children의 배율 값을 나타냅니다.
- x와 y값을 동일하게 입력할 경우 비율이 유지되며, 두 값이 다를 경우 그 반대입니다.
- Angle
- Double이며 anchor를 기준으로 하여 회전된 각도이며 radian으로 나타냅니다..
- 해당 값은 부모의 각도에 영향을 받습니다
- Anchor
- Position, rotation 의 기준이 되는 점입니다.
- 기본 값은 top left 입니다.
FlameGame
FlameGame
class는 Game
에 기반한 Component
입니다. 해당 class는 Game
에 포함된 모든 객체에 있는 update
, render
함수를 호출합니다. Low-level API
추상 클래스인
Game
은 게임 엔진의 구조를 직접 구현하고자 할 때 사용하는 저수준 API입니다. Game
클래스는 예를 들어 update
나 render
와 같은 기능을 기본적으로 구현하지 않으며, 이러한 기능은 개발자가 직접 정의해야 합니다.이 클래스는 또한
onLoad
, onMount
, onRemove
와 같은 라이프사이클 메서드를 포함하고 있습니다. 이러한 메서드들은 GameWidget
이나 다른 부모 위젯에서 게임이 로드되고 마운트될 때, 혹은 제거될 때 호출됩니다.onLoad
: 이 메서드는 클래스가 부모에 처음 추가될 때 한 번만 호출됩니다.
onMount
:onLoad
가 호출된 이후에 호출되며, 이 클래스가 새로운 부모에 추가될 때마다 실행됩니다.
onRemove
: 이 메서드는 클래스가 부모에서 제거될 때 호출됩니다.
GameWidget
GameWidget
은 Flutter widget으로, Game
instance를 Flutter의 widget treee에 삽입하는 데 사용됩니다. GameWidget
은 충분히 많은 기능을 제공하므로 Flutter application의 root로 실행될 수 있습니다. 따라서 GameWidget
을 사용하는 가장 간단한 방법은 다음과 같습니다:
위와 같이
GameWidget
을 runApp
에 전달하여 MyGame
인스턴스를 실행할 수 있습니다.동시에
GameWidget
은 일반적인 Flutter 위젯이므로, Flutter 위젯 트리 내에 어디든 삽입될 수 있습니다. 하나의 앱 내에서 여러 개의 GameWidget
을 사용하는 것도 가능합니다.이 위젯의 레이아웃 동작은 가능한 모든 공간을 채우도록 확장됩니다. 따라서 루트 위젯으로 사용될 경우 앱이 전체 화면을 차지하게 됩니다. 다른 레이아웃 위젯 내에 삽입될 경우에도 가능한 많은 공간을 차지하려고 합니다.
Asset
Flame은 표준 Flutter 의 asset directory 이용하여 audio, images 와 같은 resources를 저장합니다.
해당 파일들은 모두 pubspec.yaml 파일 내에 기재되어야 합니다.
간단 실습
Flame 을 활용한 Racing Game Demo 개발에 대한 글을 참고하며 실제 코드를 작성해보았습니다.
구현 시 추가 학습 내용
Collision Detection
- FlameGame을 상속 받은 class에
HasCollisionDetection
mixin 을 추가하면 해당 게임에서 Collision detection 관련 기능이 활성화됩니다.
- Collision Detection 을 적용할 객체에는
CollisionCallbacks
mixin 을 추가하여 Collider 가 포함된 component임을 선언합니다. - 충돌하는 두 object 모두
CollisionCallbacks
를 포함해야 합니다.
onComponentTypeCheck
함수를 override 하여 노랑색 사각형(star
), 와 파란색 사각형(player
)의 충돌 시에만onCollisionStart
함수를 실행하도록 합니다.onCollisionStart
함수를 override 하여 충돌체가player
인 경우 해당star
object를 부모로부터 삭제하도록 합니다.
Tap 인식 및 좌표 값 이용
- FlameGame을 상속 받은 class에
TapCallbacks
mixin 을 추가하면 해당 게임에서 관련 기능이 활성화됩니다.
onTapDown
은 Tap 시 1회 발생하는 함수로, parameter로는TapDowEvent
가 있습니다.
TapDownEvent.canvasPosition
깂은vector2
형식으로 tap한 위치를 나타넵니다.