본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 30. · 0 Views
Flutter Flame 파티클 시스템 완벽 가이드
게임에서 폭발, 연기, 불꽃 등 화려한 특수 효과를 만드는 파티클 시스템을 단계별로 배워봅니다. Flame 엔진의 ParticleSystemComponent를 활용하여 실무에서 바로 적용 가능한 예제를 다룹니다.
목차
1. 파티클 기본 개념
게임 개발 3주차 김개발 씨는 보스 몬스터가 폭발하는 장면을 만들다가 막막해졌습니다. 그냥 이미지만 바꾸니 너무 밋밋해 보였습니다.
"어떻게 하면 폭발이 더 화려하게 보일까요?" 선배 박시니어 씨가 말했습니다. "파티클 시스템을 사용해보세요."
파티클 시스템은 수많은 작은 입자들을 동시에 생성하고 제어하여 불, 연기, 폭발 같은 복잡한 시각 효과를 만드는 기술입니다. 마치 불꽃놀이가 하나의 폭죽에서 수백 개의 불꽃이 튀어나가듯이, 파티클 시스템도 하나의 지점에서 수많은 작은 입자를 방출합니다.
이를 통해 게임에 생동감과 화려함을 더할 수 있습니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'dart:ui';
// 기본 파티클 생성 예제
final particle = Particle.generate(
// 10개의 작은 원을 생성합니다
count: 10,
lifespan: 2.0, // 2초 동안 살아있습니다
generator: (index) => AcceleratedParticle(
// 각 파티클의 속도를 설정합니다
speed: Vector2(0, -100),
child: CircleParticle(
radius: 5.0,
paint: Paint()..color = const Color(0xFFFF0000),
),
),
);
김개발 씨는 게임 개발을 시작한 지 3주가 지났습니다. 간단한 슈팅 게임을 만들고 있는데, 적이 파괴될 때 그냥 사라지기만 해서 뭔가 허전했습니다.
유명한 게임들을 보면 폭발할 때 화려한 효과가 나타나는데, 어떻게 만드는 걸까요? 박시니어 씨가 김개발 씨의 화면을 보며 말했습니다.
"게임에 생동감을 주려면 파티클 시스템이 필수예요. 한번 배워볼까요?" 파티클 시스템이란 정확히 무엇일까요?
쉽게 비유하자면, 파티클 시스템은 마치 분수대와 같습니다. 분수대 하나에서 수백 개의 물방울이 쏟아져 나오고, 각 물방울은 서로 다른 방향으로 날아가며 중력에 의해 떨어집니다.
파티클 시스템도 이와 똑같이 하나의 지점에서 수많은 작은 입자들을 만들어내고, 각각에 속도와 생명 시간을 부여하여 제어합니다. 파티클 시스템이 없던 시절에는 어땠을까요?
개발자들은 폭발 효과를 만들려면 미리 만들어진 애니메이션 이미지를 사용해야 했습니다. 하지만 이 방법은 메모리를 많이 차지했고, 매번 같은 패턴으로 폭발해서 지루해 보였습니다.
더 큰 문제는 폭발 방향이나 강도를 코드로 조절할 수 없다는 점이었습니다. 작은 적과 큰 보스가 똑같은 폭발 효과를 내는 것은 어색했습니다.
바로 이런 문제를 해결하기 위해 파티클 시스템이 등장했습니다. 파티클 시스템을 사용하면 실시간으로 입자를 생성하여 메모리를 절약할 수 있습니다.
또한 매번 다른 패턴으로 효과를 만들어 자연스러움을 더할 수 있습니다. 무엇보다 코드로 세밀하게 제어할 수 있어서 상황에 맞는 효과를 연출할 수 있습니다.
위의 코드를 한 줄씩 살펴보겠습니다. 먼저 Particle.generate 메서드로 파티클을 생성합니다.
count 매개변수로 10개의 입자를 만들도록 지정했습니다. lifespan은 각 파티클이 화면에 머무는 시간으로, 2초 후에는 자동으로 사라집니다.
generator 함수는 각 파티클이 어떻게 동작할지 정의하는데, AcceleratedParticle로 속도를 부여하고 CircleParticle로 빨간 원 모양을 그립니다. 실제 현업에서는 어떻게 활용할까요?
모바일 RPG 게임을 개발한다고 가정해봅시다. 스킬 이펙트, 아이템 획득 효과, 몬스터 사망 연출 등 수많은 곳에서 파티클 시스템이 필요합니다.
유명 게임사들은 전담 이펙트 아티스트가 있을 정도로 파티클 시스템을 중요하게 다룹니다. Flame 엔진의 파티클 시스템을 활용하면 AAA급 게임의 효과도 충분히 구현할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 파티클을 너무 많이 생성하는 것입니다.
한 번에 수천 개의 파티클을 만들면 모바일 기기에서 프레임이 크게 떨어질 수 있습니다. 따라서 적절한 개수로 조절하고, 화면 밖으로 나간 파티클은 즉시 제거해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 게임이 화려해 보이는 거였군요!" 파티클 시스템의 기본 개념을 이해하면 게임에 생동감을 더할 수 있습니다. 다음 섹션에서는 Flame 엔진에서 파티클을 실제로 화면에 표시하는 방법을 배워보겠습니다.
실전 팁
💡 - 파티클 개수는 모바일 기기 기준 한 번에 50~100개가 적당합니다
- lifespan을 너무 길게 설정하면 메모리 누수가 발생할 수 있습니다
2. ParticleSystemComponent
김개발 씨가 파티클을 만들긴 했는데, 화면에 아무것도 나타나지 않았습니다. "파티클을 만들었는데 왜 안 보이죠?" 박시니어 씨가 웃으며 말했습니다.
"파티클은 만들었지만 게임 화면에 추가하지 않았기 때문이에요. ParticleSystemComponent가 필요해요."
ParticleSystemComponent는 Flame 엔진에서 파티클을 실제 게임 화면에 렌더링하는 컴포넌트입니다. 마치 그림을 그렸다고 해서 저절로 액자에 걸리지 않듯이, 파티클도 Component로 감싸서 게임에 추가해야 화면에 나타납니다.
이 컴포넌트는 파티클의 생명주기를 관리하고 자동으로 제거해줍니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'package:flame/game.dart';
import 'dart:ui';
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// 파티클 시스템 컴포넌트 생성
final particleComponent = ParticleSystemComponent(
particle: CircleParticle(
radius: 10.0,
paint: Paint()..color = const Color(0xFFFFAA00),
),
// 화면 중앙에 배치합니다
position: size / 2,
);
// 게임에 추가해야 화면에 표시됩니다
add(particleComponent);
}
}
김개발 씨는 열심히 파티클 코드를 작성했지만, 실행해도 화면에 아무것도 나타나지 않았습니다. 코드에 오류도 없는데 왜 그럴까요?
몇 시간을 고민하다가 결국 선배에게 물어봤습니다. 박시니어 씨가 코드를 보더니 바로 이유를 찾았습니다.
"파티클 객체만 만들고 게임에 추가하지 않았네요. 이건 마치 케이크를 만들었는데 접시에 담지 않은 거랑 같아요." ParticleSystemComponent란 무엇일까요?
쉽게 비유하자면, ParticleSystemComponent는 마치 액자와 같습니다. 아무리 멋진 그림을 그렸어도 벽에 걸지 않으면 사람들이 볼 수 없습니다.
마찬가지로 파티클을 만들었어도 ParticleSystemComponent로 감싸서 게임에 추가해야 비로소 화면에 나타납니다. 이 컴포넌트는 파티클의 위치, 크기, 회전 같은 속성도 함께 관리합니다.
Flame 엔진의 컴포넌트 시스템은 어떻게 동작할까요? Flame 게임은 Component들의 집합으로 이루어져 있습니다.
스프라이트도 컴포넌트, 충돌 판정도 컴포넌트, 파티클도 컴포넌트입니다. 게임 엔진은 매 프레임마다 모든 컴포넌트의 update와 render 메서드를 호출합니다.
따라서 파티클이 화면에 그려지려면 반드시 게임의 컴포넌트 트리에 포함되어야 합니다. ParticleSystemComponent의 강력한 점은 무엇일까요?
첫째, 자동 생명주기 관리가 가능합니다. 파티클의 수명이 다하면 컴포넌트가 자동으로 게임에서 제거됩니다.
개발자가 일일이 제거 코드를 작성할 필요가 없습니다. 둘째, Transform 기능을 사용할 수 있습니다.
위치, 회전, 크기를 자유롭게 조절할 수 있어 다양한 연출이 가능합니다. 셋째, 다른 컴포넌트와 조합할 수 있습니다.
예를 들어 PositionComponent를 상속받아 충돌 판정도 추가할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
MyGame 클래스는 FlameGame을 상속받은 메인 게임 클래스입니다. onLoad 메서드는 게임이 시작될 때 한 번 호출되는데, 여기서 초기 설정을 합니다.
ParticleSystemComponent 생성자에 particle 매개변수로 어떤 파티클을 표시할지 전달하고, position으로 화면 어디에 배치할지 정합니다. 마지막으로 add 메서드로 게임에 추가하면 매 프레임마다 파티클이 렌더링됩니다.
실제 게임에서는 어떻게 활용할까요? 슈팅 게임에서 적이 파괴될 때를 생각해봅시다.
적의 현재 위치에서 폭발 효과를 내야 합니다. 이때 ParticleSystemComponent를 생성하면서 position을 적의 좌표로 설정하면 됩니다.
파티클이 모두 사라지면 컴포넌트도 자동으로 제거되므로, 메모리 관리 걱정 없이 계속 폭발 효과를 만들 수 있습니다. 주의해야 할 점이 있습니다.
ParticleSystemComponent를 생성할 때마다 새로운 객체가 메모리에 할당됩니다. 만약 초당 60번 폭발이 일어난다면 60개의 컴포넌트가 생성됩니다.
대부분은 자동으로 제거되지만, 실수로 파티클의 lifespan을 무한대로 설정하면 컴포넌트가 계속 쌓여서 메모리 누수가 발생합니다. 따라서 항상 적절한 수명을 설정해야 합니다.
김개발 씨가 코드를 수정하고 실행했습니다. "와, 드디어 화면에 나타났어요!" 노란색 원이 화면 중앙에 반짝이며 나타났습니다.
ParticleSystemComponent를 이해하면 파티클을 자유자재로 게임에 배치할 수 있습니다. 이제 파티클이 어떻게 움직일지 제어하는 방법을 배워보겠습니다.
실전 팁
💡 - position을 지정하지 않으면 기본적으로 (0, 0) 좌표에 생성됩니다
- 한 번에 여러 파티클 효과를 중첩하려면 여러 개의 ParticleSystemComponent를 추가하세요
3. 파티클 동작 설정
파티클이 화면에 나타나긴 했는데, 가만히 있기만 했습니다. "폭발 효과를 만들고 싶은데, 파티클이 사방으로 튀어나가게 할 수는 없나요?" 김개발 씨가 물었습니다.
박시니어 씨가 대답했습니다. "그럼 AcceleratedParticle과 MovingParticle을 배워야 해요."
파티클의 동작 설정은 속도, 가속도, 회전 등을 정의하여 파티클이 어떻게 움직일지 결정하는 것입니다. AcceleratedParticle은 중력이나 바람처럼 가속도를 적용하고, MovingParticle은 일정한 속도로 움직입니다.
이를 조합하면 현실감 있는 물리 효과를 만들 수 있습니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'dart:ui';
import 'dart:math';
// 랜덤한 방향으로 튀어나가는 파티클 생성
final random = Random();
final explosionParticle = Particle.generate(
count: 30,
lifespan: 1.5,
generator: (index) {
// 랜덤한 각도 계산
final angle = random.nextDouble() * pi * 2;
final speed = 100 + random.nextDouble() * 100;
return AcceleratedParticle(
// 속도: 각도에 따라 방향 결정
speed: Vector2(cos(angle), sin(angle)) * speed,
// 가속도: 중력 효과
acceleration: Vector2(0, 200),
child: CircleParticle(radius: 3.0, paint: Paint()..color = const Color(0xFFFF5500)),
);
},
);
김개발 씨는 파티클이 화면에 나타나는 것까지는 성공했습니다. 하지만 그냥 한 점에 고정되어 있어서 전혀 폭발 같지 않았습니다.
"게임에서 본 폭발은 사방으로 흩어지던데, 어떻게 하면 되죠?" 박시니어 씨가 설명을 시작했습니다. "파티클에 움직임을 주려면 속도와 가속도를 설정해야 해요.
실제 물리 법칙처럼 말이죠." 파티클의 동작 설정이란 무엇일까요? 현실 세계의 물체는 속도를 가지고 움직이고, 중력이나 공기 저항의 영향을 받습니다.
파티클도 마찬가지입니다. 속도(speed)는 파티클이 어느 방향으로 얼마나 빠르게 움직일지 결정합니다.
가속도(acceleration)는 시간이 지나면서 속도가 어떻게 변할지 정합니다. 예를 들어 위로 던진 공은 처음에는 빠르게 올라가지만, 중력 때문에 점점 느려지다가 결국 떨어집니다.
Flame의 파티클 시스템은 어떤 종류가 있을까요? MovingParticle은 가장 기본적인 형태로, 설정한 속도로 일정하게 움직입니다.
마치 우주 공간에서 물체가 마찰 없이 계속 직진하는 것과 같습니다. AcceleratedParticle은 여기에 가속도를 추가합니다.
중력, 바람, 자석 같은 힘을 시뮬레이션할 수 있습니다. RotatingParticle은 회전 효과를 추가하고, ScalingParticle은 크기 변화를 줍니다.
이들을 중첩해서 사용하면 매우 복잡한 효과도 만들 수 있습니다. 랜덤 값을 활용하면 어떤 효과를 낼 수 있을까요?
위의 코드를 보면 Random 클래스를 사용합니다. 매번 같은 패턴으로 폭발하면 금방 지루해집니다.
하지만 각도와 속도를 랜덤하게 설정하면 매번 다른 모양의 폭발이 만들어집니다. 실제 폭발도 완전히 예측 불가능하게 일어나기 때문에, 이런 랜덤성이 현실감을 더해줍니다.
위의 코드를 자세히 분석해봅시다. Particle.generate로 30개의 파티클을 생성합니다.
generator 함수 안에서 random.nextDouble()은 0.0에서 1.0 사이의 랜덤 값을 반환합니다. 이를 pi * 2와 곱하면 0도에서 360도 사이의 랜덤 각도가 나옵니다.
cos와 sin 함수로 각도를 x, y 좌표로 변환하면 원 위의 한 점이 됩니다. 즉, 파티클이 사방으로 고르게 퍼져나갑니다.
speed는 100~200 사이의 랜덤 값으로 설정해서 일부는 멀리, 일부는 가까이 날아갑니다. acceleration의 y값을 200으로 설정해서 중력처럼 아래로 떨어지게 만듭니다.
실전에서는 어떻게 활용할까요? 액션 게임에서 플레이어가 점프할 때 발 밑에서 먼지가 일어나는 효과를 만든다고 가정해봅시다.
AcceleratedParticle로 먼지 입자를 위로 올리고, 중력 가속도로 다시 떨어뜨리면 자연스러운 먼지 효과가 완성됩니다. 또는 마법 주문을 시전할 때 손에서 반짝이는 입자가 위로 올라가는 효과도 비슷한 방식으로 구현할 수 있습니다.
초보자들이 자주 하는 실수가 있습니다. 속도 값을 너무 크게 설정하면 파티클이 한 프레임 만에 화면 밖으로 사라져버립니다.
반대로 너무 작으면 거의 움직이지 않아 효과가 밋밋해 보입니다. 여러 번 실험하면서 적절한 값을 찾아야 합니다.
일반적으로 모바일 게임에서는 50~200 정도가 적당합니다. 김개발 씨가 코드를 실행했습니다.
"오! 이제 진짜 폭발 같아요!" 30개의 파티클이 사방으로 튀어나가다가 중력에 의해 아래로 떨어졌습니다.
파티클의 동작 설정을 이해하면 다양한 물리 효과를 시뮬레이션할 수 있습니다. 이제 실전 예제로 폭발 효과를 완성해봅시다.
실전 팁
💡 - Vector2(0, 0)은 정지 상태, Vector2(100, 0)은 오른쪽으로 이동합니다
- 가속도를 음수로 설정하면 반대 방향 힘을 줄 수 있습니다
4. 폭발 효과 만들기
드디어 본격적인 폭발 효과를 만들 차례입니다. 김개발 씨는 지금까지 배운 내용을 총동원해야 했습니다.
"폭발은 파티클만 있으면 되는 게 아니라, 색상 변화와 크기 변화도 필요해요." 박시니어 씨가 조언했습니다.
폭발 효과는 여러 종류의 파티클을 조합하고, 색상 변화와 크기 변화를 추가하여 현실감 있는 폭발을 구현하는 것입니다. ComposedParticle로 여러 파티클을 겹치고, ScalingParticle과 FadingParticle로 시각적 완성도를 높입니다.
폭발 중심은 크고 밝게, 외곽은 작고 어둡게 만들면 입체감이 생깁니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'dart:ui';
import 'dart:math';
ParticleSystemComponent createExplosion(Vector2 position) {
final random = Random();
return ParticleSystemComponent(
particle: Particle.generate(
count: 50,
lifespan: 2.0,
generator: (index) {
final angle = random.nextDouble() * pi * 2;
final speed = 50 + random.nextDouble() * 150;
// 여러 효과를 조합합니다
return AcceleratedParticle(
speed: Vector2(cos(angle), sin(angle)) * speed,
acceleration: Vector2(0, 100),
child: ComposedParticle(children: [
// 크기가 점점 작아집니다
ScalingParticle(
to: 0.1,
lifespan: 2.0,
child: CircleParticle(
radius: 5.0,
paint: Paint()..color = Color.lerp(
const Color(0xFFFFFF00), // 노란색에서
const Color(0xFFFF0000), // 빨간색으로
random.nextDouble(),
)!,
),
),
]),
);
},
),
position: position,
);
}
김개발 씨는 이제 진짜 폭발 효과를 만들 준비가 되었습니다. 지금까지 파티클의 기본, 컴포넌트 추가, 동작 설정까지 배웠습니다.
하지만 실제 게임의 폭발은 단순히 점들이 흩어지는 게 아니라, 색이 변하고 크기도 변합니다. 박시니어 씨가 실제 폭발 영상을 보여줬습니다.
"보세요. 폭발 순간에는 밝은 노란색이었다가 점점 어두운 빨간색으로 변하죠?
그리고 처음엔 크다가 점점 작아집니다. 이런 세밀한 변화가 현실감을 만들어요." 폭발 효과의 핵심 요소는 무엇일까요?
첫째, 색상 그라데이션입니다. 실제 폭발은 중심부가 가장 뜨겁고 밝습니다.
시간이 지나면서 식으면서 색이 변합니다. 둘째, 크기 변화입니다.
폭발 초기에는 에너지가 크지만 점차 약해지면서 파편도 작아집니다. 셋째, 속도 분포입니다.
모든 파편이 같은 속도로 날아가면 부자연스럽습니다. 일부는 빠르게, 일부는 느리게 날아가야 합니다.
ComposedParticle은 어떻게 사용할까요? ComposedParticle은 여러 개의 파티클을 하나로 합치는 컨테이너입니다.
마치 포토샵의 레이어처럼 여러 효과를 겹칠 수 있습니다. 예를 들어 회전하면서 크기가 변하는 파티클을 만들려면, RotatingParticle 안에 ScalingParticle을 넣고, 그 안에 실제 그래픽 파티클을 넣으면 됩니다.
이런 식으로 복잡한 효과를 단계적으로 조합할 수 있습니다. Color.lerp는 무엇인가요?
lerp는 'linear interpolation'의 약자로, 두 값 사이를 선형 보간하는 함수입니다. 쉽게 말해 두 색상 사이의 중간 색을 계산합니다.
Color.lerp(노란색, 빨간색, 0.5)는 노란색과 빨간색의 정확히 중간 색인 주황색을 반환합니다. 랜덤 값을 사용하면 각 파티클마다 다른 색상이 지정되어 더 다채로운 폭발 효과를 만들 수 있습니다.
코드를 단계별로 분석해봅시다. createExplosion 함수는 위치를 받아서 그 지점에서 폭발을 만듭니다.
50개의 파티클을 생성하는데, 각각 0도에서 360도 사이의 랜덤 방향으로 날아갑니다. ScalingParticle의 to 매개변수를 0.1로 설정해서 원래 크기의 10%까지 줄어듭니다.
Color.lerp로 노란색과 빨간색 사이의 랜덤한 색을 선택합니다. 이렇게 하면 어떤 파티클은 노란색에 가깝고, 어떤 파티클은 빨간색에 가까워서 폭발의 깊이감이 생깁니다.
실전 게임에서는 어떻게 활용할까요? 적이 파괴되는 순간을 처리하는 코드를 생각해봅시다.
적의 onDestroy 메서드에서 createExplosion(enemy.position)을 호출하면 됩니다. 게임에 폭발 컴포넌트가 추가되고, 2초 후 자동으로 제거됩니다.
추가로 효과음을 재생하고 화면을 흔드는 효과를 함께 넣으면 더욱 강렬한 연출이 완성됩니다. 성능 최적화 팁도 알려드릴게요.
모바일 게임에서 동시에 폭발이 여러 개 일어나면 부담이 됩니다. 화면에 보이지 않는 폭발은 생성하지 않는 것이 좋습니다.
또한 파티클 개수를 디바이스 성능에 따라 조절할 수 있습니다. 고사양 기기는 50개, 저사양 기기는 20개로 설정하는 식입니다.
Flame의 게임 설정에서 품질 옵션을 만들어 사용자가 선택하게 할 수도 있습니다. 김개발 씨가 적을 파괴하는 버튼을 눌렀습니다.
"와! 이건 진짜 게임 같은데요!" 노란색과 빨간색의 파티클이 사방으로 퍼지면서 점점 작아지고 사라졌습니다.
폭발 효과를 만드는 방법을 익혔으니, 이제 다른 종류의 효과도 만들어봅시다. 다음은 연기 효과입니다.
실전 팁
💡 - 파티클 개수를 늘리면 화려하지만 성능이 떨어지므로 적절히 조절하세요
- 폭발 크기는 게임 오브젝트 크기에 비례하게 만들면 자연스럽습니다
5. 연기 효과
폭발 효과를 완성한 김개발 씨는 욕심이 생겼습니다. "폭발 후에 연기가 피어오르면 더 멋질 것 같아요." 박시니어 씨가 고개를 끄덕였습니다.
"연기는 폭발과 다르게 천천히 위로 올라가면서 퍼져야 해요. 속도와 투명도 설정이 핵심이죠."
연기 효과는 파티클이 천천히 위로 올라가면서 점점 투명해지고 커지는 효과입니다. 폭발처럼 빠르게 퍼지지 않고, FadingParticle로 서서히 사라지게 만듭니다.
연기는 지속적으로 생성되어야 하므로 지속 생성 패턴을 사용합니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'dart:ui';
import 'dart:math';
class SmokeEmitter extends Component {
final Vector2 position;
final Random random = Random();
double elapsed = 0;
SmokeEmitter(this.position);
@override
void update(double dt) {
super.update(dt);
elapsed += dt;
// 0.1초마다 연기 파티클 생성
if (elapsed >= 0.1) {
elapsed = 0;
_emitSmoke();
}
}
void _emitSmoke() {
final particle = ParticleSystemComponent(
particle: AcceleratedParticle(
speed: Vector2(random.nextDouble() * 20 - 10, -50), // 위로 올라감
acceleration: Vector2(0, -20), // 점점 더 느려짐
child: ScalingParticle(
to: 2.0, // 크기가 2배로 커짐
curve: Curves.easeOut,
child: FadingParticle(
// 투명도가 1에서 0으로
child: CircleParticle(
radius: 8.0,
paint: Paint()..color = const Color(0x88888888),
),
),
),
lifespan: 3.0,
),
position: position.clone(),
);
parent?.add(particle);
}
}
김개발 씨는 폭발 효과에 만족했지만, 뭔가 아쉬웠습니다. 실제 폭발 영상을 보면 폭발 후에 연기가 피어오릅니다.
하지만 지금은 폭발이 끝나면 아무것도 남지 않습니다. 박시니어 씨가 설명했습니다.
"폭발과 연기는 성질이 달라요. 폭발은 한 번에 터지는 거고, 연기는 계속 피어오르는 거죠.
그래서 구현 방법도 다릅니다." 연기 효과의 특징은 무엇일까요? 실제 연기를 관찰해보면 몇 가지 특징이 있습니다.
첫째, 천천히 위로 올라갑니다. 폭발처럼 빠르게 사방으로 튀지 않습니다.
둘째, 점점 퍼지면서 커집니다. 처음엔 작고 뭉쳐있다가 공기와 섞이면서 넓게 퍼집니다.
셋째, 투명해지면서 사라집니다. 완전히 희미해질 때까지 서서히 흩어집니다.
지속 생성 패턴이란 무엇일까요? 폭발은 한 순간에 모든 파티클을 생성합니다.
하지만 연기는 지속적으로 만들어져야 합니다. 촛불에서 연기가 계속 나오듯이, 일정 시간마다 새로운 파티클을 추가해야 합니다.
이를 위해 Component를 상속받아 update 메서드에서 타이머를 관리합니다. 0.1초마다 파티클을 하나씩 추가하면 자연스러운 연기 흐름이 만들어집니다.
FadingParticle은 어떻게 동작할까요? FadingParticle은 파티클의 투명도를 시간에 따라 조절합니다.
lifespan 동안 opacity가 1.0에서 0.0으로 선형적으로 감소합니다. 이렇게 하면 파티클이 서서히 사라지는 효과를 낼 수 있습니다.
연기뿐만 아니라 마법 효과, 영혼 같은 것을 표현할 때도 유용합니다. 코드를 자세히 살펴봅시다.
SmokeEmitter 클래스는 Component를 상속받아 게임 루프에 참여합니다. elapsed 변수로 경과 시간을 추적하고, 0.1초마다 _emitSmoke 메서드를 호출합니다.
speed의 y값을 -50으로 설정해서 위로 올라가게 만들고, x값을 -10~10 사이 랜덤으로 설정해서 약간 흔들리며 올라갑니다. ScalingParticle의 to를 2.0으로 설정해서 크기가 2배로 커지고, Curves.easeOut으로 처음엔 빠르게 커지다가 나중엔 천천히 커집니다.
Paint의 색상을 0x88888888로 설정했는데, 앞의 88은 투명도입니다. 반투명 회색으로 시작해서 FadingParticle에 의해 완전 투명까지 사라집니다.
실제 게임에서는 어떻게 활용할까요? 자동차 레이싱 게임에서 차의 배기구에서 연기가 나오는 효과를 만든다고 가정해봅시다.
차 컴포넌트에 SmokeEmitter를 자식으로 추가하고, 배기구의 상대 위치를 설정하면 됩니다. 차가 움직여도 연기 방출 위치가 따라 움직입니다.
엔진 과열 상태에서는 연기 색을 검게, 정상 상태에서는 하얗게 설정하여 차량 상태를 시각적으로 표현할 수도 있습니다. 성능 고려사항이 있습니다.
연기는 지속적으로 파티클을 생성하므로 관리하지 않으면 메모리가 계속 증가합니다. 다행히 각 파티클은 3초 후 자동으로 제거되므로 일정 개수 이상 쌓이지 않습니다.
하지만 화면에 연기 효과가 10개 이상 동시에 있으면 부담이 될 수 있습니다. 따라서 연기 효과를 멈춰야 할 때는 SmokeEmitter 컴포넌트를 게임에서 제거해야 합니다.
김개발 씨가 연기 효과를 실행했습니다. "오!
정말 연기가 피어오르네요!" 회색 파티클들이 천천히 위로 올라가면서 커지고 투명해졌습니다. 연기 효과를 만드는 방법을 배웠습니다.
이제 마지막으로 완전히 커스텀한 파티클을 만드는 방법을 알아봅시다.
실전 팁
💡 - 연기 색상을 흰색(0xFFFFFFFF)으로 하면 증기, 검은색(0xFF000000)으로 하면 매연이 됩니다
- 생성 간격을 조절하여 연기의 농도를 변경할 수 있습니다
6. 커스텀 파티클 생성
기본 파티클들로 많은 효과를 만들 수 있지만, 김개발 씨는 더 특별한 효과를 원했습니다. "별 모양 파티클이나 이미지 파티클을 만들 수는 없나요?" 박시니어 씨가 미소 지었습니다.
"당연히 가능하죠. Particle 클래스를 상속받아 직접 만들 수 있어요."
커스텀 파티클은 Particle 클래스를 상속받아 원하는 모양과 동작을 직접 구현하는 것입니다. render 메서드를 오버라이드하여 Canvas API로 그릴 수 있고, update 메서드로 프레임마다 상태를 갱신할 수 있습니다.
이미지, 텍스트, 복잡한 도형 등 무엇이든 파티클로 만들 수 있습니다.
다음 코드를 살펴봅시다.
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'dart:ui';
import 'dart:math';
// 별 모양 커스텀 파티클
class StarParticle extends Particle {
final Paint paint;
final double size;
double rotation = 0;
StarParticle({required this.size, required this.paint, double? lifespan})
: super(lifespan: lifespan);
@override
void render(Canvas canvas) {
// 5개 뾰족한 별 그리기
final path = Path();
for (int i = 0; i < 5; i++) {
final angle = (i * 4 * pi / 5) + rotation;
final x = cos(angle) * size;
final y = sin(angle) * size;
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();
canvas.drawPath(path, paint);
}
@override
void update(double dt) {
super.update(dt);
// 회전 효과
rotation += dt * 2;
}
}
// 사용 예제
final starExplosion = Particle.generate(
count: 20,
generator: (i) => AcceleratedParticle(
speed: Vector2.random() * 100,
child: StarParticle(
size: 10.0,
paint: Paint()..color = const Color(0xFFFFD700),
lifespan: 2.0,
),
),
);
김개발 씨는 기본 파티클로 폭발과 연기를 만들었지만, 더 특별한 효과를 원했습니다. 예를 들어 적을 처치했을 때 별 모양 점수가 튀어나오거나, 아이템 획득 시 반짝이는 다이아몬드 모양 효과를 만들고 싶었습니다.
박시니어 씨가 말했습니다. "Flame이 제공하는 기본 파티클은 원, 사각형 정도예요.
하지만 Particle 클래스를 직접 상속받으면 무엇이든 만들 수 있습니다." 커스텀 파티클은 어떻게 만들까요? Particle 클래스를 상속받은 후 두 개의 핵심 메서드를 구현합니다.
render 메서드는 파티클을 화면에 그리는 역할을 합니다. Flutter의 Canvas API를 사용하여 선, 도형, 이미지, 텍스트 등 무엇이든 그릴 수 있습니다.
update 메서드는 매 프레임마다 호출되어 파티클의 상태를 갱신합니다. 회전 각도를 변경하거나, 색상을 조절하거나, 크기를 바꾸는 등의 애니메이션을 구현할 수 있습니다.
Canvas API로 무엇을 그릴 수 있을까요? Flutter의 Canvas는 매우 강력한 2D 그래픽 API입니다.
drawCircle로 원을, drawRect로 사각형을, drawPath로 복잡한 도형을 그릴 수 있습니다. drawImage로 스프라이트 이미지를 그릴 수도 있고, drawParagraph로 텍스트도 렌더링할 수 있습니다.
심지어 그라데이션, 블렌드 모드, 그림자 효과까지 적용 가능합니다. 이 모든 기능을 파티클에 활용할 수 있습니다.
위의 코드를 단계별로 분석해봅시다. StarParticle 클래스는 Particle을 상속받고, 생성자에서 크기와 색상을 받습니다.
render 메서드에서는 Path 객체로 별 모양을 그립니다. 5개의 점을 이어서 별을 만드는데, 각 점의 위치를 삼각함수로 계산합니다.
rotation 변수를 각도에 더해서 회전 효과를 줍니다. update 메서드에서는 dt(델타 타임, 지난 프레임 이후 경과 시간)를 사용하여 rotation 값을 증가시킵니다.
dt * 2는 초당 2 라디안씩 회전한다는 뜻입니다. 이미지 파티클은 어떻게 만들까요?
Flame에는 ImageParticle이 기본 제공됩니다. Sprite나 Image 객체를 파티클로 사용할 수 있습니다.
예를 들어 코인 이미지를 로드한 후, ImageParticle로 감싸면 코인이 튀어나오는 효과를 만들 수 있습니다. 이를 RotatingParticle이나 ScalingParticle과 조합하면 회전하면서 커지는 코인 효과도 가능합니다.
실전 활용 사례를 살펴봅시다. RPG 게임에서 크리티컬 히트를 표시한다고 가정해봅시다.
데미지 숫자가 파티클처럼 위로 올라가면서 사라지는 효과를 만들 수 있습니다. TextPainter로 숫자를 렌더링하는 커스텀 파티클을 만들고, MovingParticle과 FadingParticle로 감싸면 됩니다.
일반 공격은 흰색, 크리티컬은 노란색으로 구분하면 더 명확합니다. 성능 최적화 방법도 알아둡시다.
커스텀 파티클의 render 메서드는 매 프레임마다 호출됩니다. 따라서 여기서 무거운 연산을 하면 프레임이 떨어집니다.
Path나 Paint 객체는 생성자에서 미리 만들어두고 재사용하는 것이 좋습니다. 또한 이미지를 매번 로드하지 말고 캐싱해야 합니다.
Flame의 이미지 캐시를 활용하면 편리합니다. 주의할 점이 있습니다.
render 메서드에서 Canvas에 그릴 때 좌표 (0, 0)이 파티클의 중심입니다. 별 모양 코드에서 cos와 sin의 결과가 -size에서 +size 사이이므로, 별이 중심을 기준으로 그려집니다.
만약 왼쪽 위 모서리를 기준으로 그리고 싶다면 좌표를 조정해야 합니다. 김개발 씨가 별 파티클 폭발을 실행했습니다.
"와! 별들이 회전하면서 날아가네요!" 황금색 별 20개가 사방으로 튀면서 빙글빙글 돌았습니다.
이제 김개발 씨는 파티클 시스템의 모든 것을 알게 되었습니다. 기본 파티클부터 커스텀 파티클까지, 폭발부터 연기까지 자유자재로 만들 수 있습니다.
박시니어 씨가 마지막으로 말했습니다. "파티클 시스템은 게임의 화룡점정이에요.
작은 효과 하나가 게임의 느낌을 완전히 바꿀 수 있습니다. 이제 여러분만의 멋진 효과를 만들어보세요!"
실전 팁
💡 - Canvas API 문서를 참고하면 더 다양한 그리기 기능을 활용할 수 있습니다
- 복잡한 도형은 Path.combine으로 여러 Path를 합칠 수 있습니다
- 파티클의 progress 속성(0.0~1.0)을 활용하면 생명 주기에 따른 애니메이션을 만들 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Flame 게임 입력 처리 완벽 가이드
Flutter의 Flame 엔진에서 터치, 드래그, 키보드 등 다양한 입력을 처리하는 방법을 배웁니다. 게임 개발에 필수적인 제스처 인식과 조이스틱 구현까지 실전 예제로 마스터하세요.
네트워크 동기화 고급 완벽 가이드
멀티플레이어 게임 개발에서 가장 까다로운 네트워크 동기화의 핵심 기법들을 다룹니다. 클라이언트 예측부터 P2P 아키텍처까지, 실무에서 바로 적용할 수 있는 고급 기술을 술술 읽히는 스토리로 풀어냅니다.
Flutter Flame 게임 애니메이션 완벽 가이드
Flutter의 Flame 엔진으로 스프라이트 애니메이션을 구현하는 방법을 배웁니다. 기초부터 캐릭터 걷기 애니메이션까지 단계별로 살펴봅니다. 게임 개발 입문자를 위한 실전 가이드입니다.
프로시저럴 생성으로 무한 게임 세계 만들기
게임 개발에서 매번 손으로 맵을 그리는 대신, 알고리즘으로 자동 생성하는 프로시저럴 생성 기법을 배웁니다. 펄린 노이즈부터 던전 생성, 무한 맵 시스템까지 실전 예제로 익혀봅시다.
Flame 게임 물리 엔진 완벽 가이드
Flutter 게임 엔진 Flame에서 Forge2D를 활용한 물리 시뮬레이션을 초급자도 이해할 수 있도록 실무 스토리로 풀어낸 완벽 가이드입니다. 중력, 충돌, 조인트 등 게임 물리의 핵심을 배워보세요.