🤖

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

⚠️

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

이미지 로딩 중...

Flutter Flame으로 만드는 첫 번째 게임 프로젝트 - 슬라이드 1/7
A

AI Generated

2026. 2. 2. · 2 Views

Flutter Flame으로 만드는 첫 번째 게임 프로젝트

Flutter의 게임 엔진 Flame을 사용하여 첫 번째 게임을 완성하는 전체 과정을 다룹니다. 게임 기획부터 캐릭터 생성, 점수 시스템, 게임 오버 로직, 난이도 조절, 최종 빌드까지 실전 게임 개발의 모든 단계를 배웁니다.


목차

  1. 게임_기획하기
  2. 캐릭터와_적_생성
  3. 점수_시스템_구현
  4. 게임_오버_로직
  5. 난이도_조절
  6. 최종_빌드_및_테스트

1. 게임 기획하기

김개발 씨는 평소 모바일 게임을 즐겨 하던 중 문득 이런 생각이 들었습니다. "나도 게임을 만들어볼 수 있지 않을까?" Flutter를 어느 정도 다룰 줄 알게 된 지금, 드디어 첫 게임 프로젝트에 도전해보기로 합니다.

게임 기획은 한마디로 설계도를 그리는 과정입니다. 마치 건축가가 집을 짓기 전에 도면을 그리는 것과 같습니다.

어떤 게임을 만들지, 플레이어가 무엇을 조작하고, 목표가 무엇인지를 명확히 정의해야 개발 과정에서 길을 잃지 않습니다.

다음 코드를 살펴봅시다.

// 게임의 기본 구조를 정의하는 메인 게임 클래스
import 'package:flame/game.dart';
import 'package:flame/components.dart';

class SpaceShooterGame extends FlameGame {
  // 게임의 핵심 설정값들을 상수로 정의
  static const double playerSpeed = 300.0;
  static const double enemySpawnInterval = 2.0;

  @override
  Future<void> onLoad() async {
    // 게임 시작 시 필요한 리소스를 로드합니다
    await images.loadAll(['player.png', 'enemy.png', 'bullet.png']);
  }

  @override
  void update(double dt) {
    super.update(dt);
    // 매 프레임마다 게임 로직을 업데이트합니다
  }
}

김개발 씨는 입사 1년 차 주니어 개발자입니다. 회사에서 Flutter로 앱을 만드는 일에 익숙해졌지만, 항상 마음 한켠에는 게임을 만들어보고 싶다는 꿈이 있었습니다.

어느 주말, 드디어 그 꿈을 실현하기로 마음먹습니다. "그런데 게임은 어디서부터 시작해야 하지?" 김개발 씨는 인터넷을 검색하다가 Flame이라는 단어를 발견합니다.

Flutter 위에서 동작하는 게임 엔진이라고 합니다. 기존에 알던 Flutter 지식을 활용할 수 있다니, 진입 장벽이 한결 낮게 느껴집니다.

그렇다면 게임 기획이란 정확히 무엇일까요? 쉽게 비유하자면, 게임 기획은 마치 여행 계획을 세우는 것과 같습니다.

어디로 갈지, 무엇을 할지, 얼마나 머물지를 미리 정해두지 않으면 여행 중에 혼란스러워집니다. 게임도 마찬가지입니다.

플레이어가 조작하는 대상, 목표, 장애물, 승리 조건을 명확히 정해야 합니다. 김개발 씨는 간단한 슈팅 게임을 만들기로 합니다.

플레이어는 우주선을 조작하고, 위에서 내려오는 적들을 피하거나 격파합니다. 점수를 최대한 많이 모으는 것이 목표입니다.

Flame 엔진에서는 FlameGame 클래스를 상속받아 게임을 만듭니다. 이 클래스는 게임 루프를 자동으로 관리해줍니다.

매 프레임마다 update 메서드가 호출되고, 화면을 그릴 때는 render 메서드가 호출됩니다. 위 코드에서 onLoad 메서드를 보면 게임 시작 시 이미지 리소스를 미리 로드합니다.

이렇게 하면 게임 중에 이미지를 불러오느라 버벅거리는 현상을 방지할 수 있습니다. update 메서드는 게임의 심장과 같습니다.

매 프레임마다 호출되어 캐릭터 이동, 충돌 감지, 점수 계산 등 모든 게임 로직을 처리합니다. dt는 delta time의 약자로, 이전 프레임부터 현재 프레임까지 경과한 시간을 의미합니다.

실제 현업에서는 어떻게 활용할까요? 캐주얼 게임 회사에서는 이런 구조로 수십 개의 미니 게임을 만듭니다.

기본 골격은 동일하고, 게임별로 캐릭터와 규칙만 바꾸는 방식입니다. 하지만 주의할 점도 있습니다.

처음부터 너무 복잡한 게임을 기획하면 완성하지 못하고 포기하기 쉽습니다. 김개발 씨도 처음에는 RPG를 만들고 싶었지만, 현실적으로 슈팅 게임부터 시작하기로 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 기획서를 노트에 정리한 김개발 씨는 뿌듯한 마음으로 다음 단계를 준비합니다.

"좋아, 이제 캐릭터를 만들어볼까!"

실전 팁

💡 - 첫 게임은 단순하게 시작하세요. 복잡한 기획은 완성 확률을 낮춥니다.

  • 게임의 핵심 재미 요소를 한 문장으로 정의해보세요.

2. 캐릭터와 적 생성

게임의 기본 구조를 잡은 김개발 씨는 이제 화면에 무언가를 띄워보고 싶어졌습니다. 아무것도 없는 검은 화면은 너무 심심합니다.

플레이어가 조작할 우주선과 물리쳐야 할 적을 만들어야 할 때입니다.

Flame에서 화면에 표시되는 모든 객체는 Component입니다. 마치 레고 블록처럼 여러 컴포넌트를 조합하여 게임 세계를 구성합니다.

캐릭터, 적, 총알 모두 SpriteComponent를 상속받아 만들 수 있습니다.

다음 코드를 살펴봅시다.

// 플레이어 우주선 컴포넌트
class Player extends SpriteComponent with HasGameRef<SpaceShooterGame> {
  Player() : super(size: Vector2(64, 64));

  @override
  Future<void> onLoad() async {
    sprite = await gameRef.loadSprite('player.png');
    position = Vector2(gameRef.size.x / 2, gameRef.size.y - 100);
    anchor = Anchor.center;
  }

  void move(Vector2 delta) {
    position.add(delta);
    // 화면 밖으로 나가지 않도록 제한
    position.clamp(Vector2.zero(), gameRef.size - size);
  }
}

// 적 우주선 컴포넌트
class Enemy extends SpriteComponent with HasGameRef<SpaceShooterGame> {
  final double speed;

  Enemy({this.speed = 150}) : super(size: Vector2(48, 48));

  @override
  Future<void> onLoad() async {
    sprite = await gameRef.loadSprite('enemy.png');
    anchor = Anchor.center;
  }

  @override
  void update(double dt) {
    super.update(dt);
    position.y += speed * dt;
  }
}

김개발 씨는 코드 에디터를 열고 첫 번째 캐릭터를 만들기 시작합니다. 하지만 막상 시작하려니 막막합니다.

"캐릭터는 어떻게 만들지? 그냥 이미지를 띄우면 되나?" 선배 개발자 박시니어 씨가 옆을 지나가다 김개발 씨의 화면을 봅니다.

"게임 만들어? Flame은 컴포넌트 기반이야.

화면에 보이는 모든 건 컴포넌트라고 생각하면 돼." 그렇다면 컴포넌트란 정확히 무엇일까요? 쉽게 비유하자면, 컴포넌트는 마치 배우와 같습니다.

영화에서 배우는 각자의 역할이 있고, 대본에 따라 움직입니다. 게임에서 컴포넌트도 마찬가지입니다.

플레이어 컴포넌트는 사용자 입력에 반응하고, 적 컴포넌트는 정해진 패턴대로 움직입니다. Flame에서 이미지를 표시하는 컴포넌트는 SpriteComponent입니다.

이 클래스를 상속받으면 이미지를 화면에 띄우고, 위치를 조절하고, 크기를 변경할 수 있습니다. 위 코드에서 Player 클래스를 살펴봅시다.

HasGameRef 믹스인을 사용하면 gameRef를 통해 메인 게임 객체에 접근할 수 있습니다. 이를 통해 화면 크기를 알아내거나, 게임 전역 상태에 접근할 수 있습니다.

onLoad 메서드에서는 스프라이트 이미지를 불러오고 초기 위치를 설정합니다. anchor를 Anchor.center로 설정하면 position이 컴포넌트의 중심점을 가리키게 됩니다.

Enemy 클래스의 update 메서드를 보면 매 프레임마다 y 좌표를 증가시킵니다. speed에 dt를 곱하는 것이 중요합니다.

이렇게 하면 프레임 레이트와 관계없이 일정한 속도로 이동합니다. 실제 현업에서는 적의 종류를 여러 개 만들어 다양성을 줍니다.

빠른 적, 느린 적, 지그재그로 움직이는 적 등을 만들어 플레이어에게 도전 의식을 불어넣습니다. 주의할 점은 position.clamp 메서드입니다.

이걸 빠뜨리면 플레이어가 화면 밖으로 나가버릴 수 있습니다. 김개발 씨도 처음에 이 부분을 빠뜨려서 우주선이 화면 밖으로 사라지는 버그를 겪었습니다.

김개발 씨는 코드를 실행해봅니다. 드디어 화면에 우주선이 나타났습니다.

위에서 적들이 내려오는 것도 보입니다. "오, 진짜 게임 같아!" 작은 성취감에 미소가 번집니다.

실전 팁

💡 - 컴포넌트의 anchor를 center로 설정하면 위치 계산이 직관적입니다.

  • dt를 곱해 프레임 독립적인 움직임을 구현하세요.

3. 점수 시스템 구현

화면에 캐릭터가 움직이는 것을 본 김개발 씨는 한 가지 아쉬움을 느꼈습니다. "적을 아무리 피해도 뭔가 보람이 없네..." 그렇습니다.

게임에는 점수가 있어야 플레이어가 목표 의식을 가지게 됩니다.

점수 시스템은 게임의 동기 부여 장치입니다. 마치 마라톤 코스의 이정표처럼, 플레이어에게 현재 얼마나 잘하고 있는지 피드백을 줍니다.

적을 처치하면 점수가 오르고, 화면에 실시간으로 표시되어야 합니다.

다음 코드를 살펴봅시다.

// 점수를 관리하는 게임 클래스 확장
class SpaceShooterGame extends FlameGame with HasCollisionDetection {
  int score = 0;
  late TextComponent scoreText;

  @override
  Future<void> onLoad() async {
    // 점수 표시 텍스트 컴포넌트 생성
    scoreText = TextComponent(
      text: 'Score: 0',
      position: Vector2(20, 20),
      textRenderer: TextPaint(
        style: const TextStyle(
          color: Colors.white,
          fontSize: 24,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
    add(scoreText);
  }

  void addScore(int points) {
    score += points;
    scoreText.text = 'Score: $score';
  }
}

// 총알과 적의 충돌 처리
class Bullet extends SpriteComponent with CollisionCallbacks {
  @override
  void onCollision(Set<Vector2> points, PositionComponent other) {
    if (other is Enemy) {
      other.removeFromParent();
      removeFromParent();
      (gameRef as SpaceShooterGame).addScore(10);
    }
  }
}

김개발 씨는 게임을 플레이하면서 무언가 허전함을 느꼈습니다. 적을 피하고, 총알을 쏘고, 하지만 그게 다였습니다.

"게임인데 점수가 없으면 재미가 반감되지 않나?" 그날 저녁 퇴근 후, 김개발 씨는 점수 시스템을 구현하기로 합니다. 그렇다면 점수 시스템은 왜 중요할까요?

쉽게 비유하자면, 점수는 마치 성적표와 같습니다. 학생이 시험을 보고 점수를 받으면 자신의 실력을 객관적으로 알 수 있습니다.

게임에서 점수는 플레이어에게 "지금 잘하고 있어요" 또는 "더 분발해야 해요"라고 말해주는 역할을 합니다. Flame에서 텍스트를 화면에 표시하려면 TextComponent를 사용합니다.

위 코드에서 scoreText는 화면 왼쪽 상단에 고정되어 현재 점수를 보여줍니다. 점수가 바뀔 때마다 addScore 메서드가 호출됩니다.

이 메서드는 점수를 증가시키고 텍스트를 업데이트합니다. 간단하지만 게임의 핵심 기능입니다.

그런데 점수는 언제 올라가야 할까요? 바로 총알이 적에게 맞았을 때입니다.

이를 위해 충돌 감지 기능이 필요합니다. Flame에서 충돌 감지를 사용하려면 게임 클래스에 HasCollisionDetection 믹스인을 추가합니다.

그리고 충돌을 감지할 컴포넌트에 CollisionCallbacks 믹스인을 추가합니다. onCollision 메서드는 충돌이 발생할 때 호출됩니다.

other 파라미터로 충돌한 상대방 컴포넌트를 알 수 있습니다. 적과 충돌했다면 적과 총알을 모두 제거하고 점수를 올립니다.

실제 현업에서는 점수뿐만 아니라 콤보 시스템, 멀티플라이어 등 다양한 보상 메커니즘을 추가합니다. 연속으로 적을 처치하면 보너스 점수를 주는 식입니다.

주의할 점은 removeFromParent 메서드입니다. 이 메서드를 호출해야 컴포넌트가 게임에서 완전히 제거됩니다.

제거하지 않으면 보이지 않는 객체가 메모리에 쌓여 성능 문제가 발생할 수 있습니다. 김개발 씨는 점수 시스템을 구현한 후 다시 게임을 플레이합니다.

적을 처치할 때마다 점수가 올라가는 것을 보니 훨씬 재미있습니다. "100점!

200점! 와, 이거 중독성 있는데?"

실전 팁

💡 - 점수가 오를 때 효과음이나 애니메이션을 추가하면 더욱 만족스럽습니다.

  • 최고 점수를 저장하여 플레이어에게 도전 목표를 제공하세요.

4. 게임 오버 로직

점수 시스템까지 구현한 김개발 씨는 어느덧 자신의 게임에 빠져들었습니다. 그런데 문제가 있습니다.

적에게 부딪혀도 아무 일도 일어나지 않습니다. 진정한 게임이 되려면 실패도전이 있어야 합니다.

게임 오버 로직은 게임의 긴장감을 만듭니다. 마치 줄타기에서 떨어지면 끝나는 것처럼, 실패의 가능성이 있어야 성공의 기쁨도 커집니다.

플레이어가 적과 충돌하면 게임이 끝나고, 재시작할 수 있어야 합니다.

다음 코드를 살펴봅시다.

// 플레이어 충돌 처리 및 게임 오버
class Player extends SpriteComponent
    with HasGameRef<SpaceShooterGame>, CollisionCallbacks {
  int health = 3;

  @override
  Future<void> onLoad() async {
    add(CircleHitbox());  // 충돌 영역 추가
  }

  @override
  void onCollision(Set<Vector2> points, PositionComponent other) {
    if (other is Enemy) {
      health--;
      other.removeFromParent();

      if (health <= 0) {
        gameRef.gameOver();
      }
    }
  }
}

// 게임 오버 화면
class GameOverScreen extends Component with HasGameRef<SpaceShooterGame> {
  @override
  Future<void> onLoad() async {
    final gameOverText = TextComponent(
      text: 'GAME OVER',
      position: gameRef.size / 2,
      anchor: Anchor.center,
      textRenderer: TextPaint(
        style: const TextStyle(color: Colors.red, fontSize: 48),
      ),
    );

    final restartText = TextComponent(
      text: 'Tap to Restart',
      position: gameRef.size / 2 + Vector2(0, 60),
      anchor: Anchor.center,
    );

    addAll([gameOverText, restartText]);
  }
}

김개발 씨는 게임을 하면서 이상한 점을 발견합니다. 적들이 우주선을 그대로 통과해버립니다.

"이러면 게임이 아니라 그냥 구경이잖아!" 게임에는 긴장감이 필요합니다. 실패할 수 있다는 가능성이 있어야 집중하게 됩니다.

그렇다면 게임 오버 로직이란 무엇일까요? 쉽게 비유하자면, 게임 오버는 마치 축구에서 골을 허용하는 것과 같습니다.

골키퍼가 공을 막지 못하면 점수를 잃습니다. 일정 점수 이상 잃으면 경기에서 지게 됩니다.

게임도 마찬가지로 적에게 맞으면 체력이 줄고, 체력이 바닥나면 게임이 끝납니다. 위 코드에서 CircleHitbox를 주목하세요.

이것은 충돌 감지 영역을 정의합니다. 원형 히트박스를 추가하면 Flame이 자동으로 다른 히트박스와의 충돌을 계산합니다.

플레이어의 health 변수는 생명력을 나타냅니다. 적과 충돌할 때마다 1씩 감소하고, 0이 되면 gameOver 메서드를 호출합니다.

GameOverScreen은 게임이 끝났을 때 표시되는 화면입니다. 큰 빨간 글씨로 "GAME OVER"를 보여주고, 아래에 재시작 안내 문구를 표시합니다.

재시작 기능은 어떻게 구현할까요? 화면을 탭하면 모든 컴포넌트를 제거하고 게임을 초기화하면 됩니다.

점수도 0으로, 체력도 초기값으로 되돌립니다. 실제 현업에서는 게임 오버 시 광고를 보면 부활하는 기능을 추가하기도 합니다.

또는 최고 점수를 서버에 저장하여 리더보드를 구성합니다. 주의할 점은 히트박스 크기입니다.

히트박스가 스프라이트보다 크면 플레이어는 불공평하다고 느낍니다. 반대로 너무 작으면 적이 몸을 통과하는 것처럼 보입니다.

적절한 균형을 찾아야 합니다. 김개발 씨는 게임 오버 기능을 추가한 후 다시 플레이합니다.

이제 적을 피해야 하는 이유가 생겼습니다. 심장이 두근거립니다.

"와, 이제 진짜 게임 같다!"

실전 팁

💡 - 피격 시 무적 시간을 주어 연속 데미지를 방지하세요.

  • 게임 오버 전에 화면 효과(흔들림, 플래시)를 추가하면 임팩트가 커집니다.

5. 난이도 조절

게임 오버까지 구현한 김개발 씨는 친구에게 게임을 보여줬습니다. 친구는 처음엔 재미있어 했지만 곧 지루해했습니다.

"너무 쉬운데? 적이 느려서 피하기 쉬워." 김개발 씨는 깨달았습니다.

시간이 지날수록 난이도가 올라가야 한다는 것을.

난이도 조절은 게임의 수명을 결정합니다. 마치 러닝머신의 속도를 점점 올리는 것처럼, 플레이어가 적응할 때쯤 더 큰 도전을 제시해야 합니다.

시간이 지날수록 적이 빨라지고, 더 자주 나타나야 게임이 지루해지지 않습니다.

다음 코드를 살펴봅시다.

// 난이도를 관리하는 시스템
class DifficultyManager extends Component with HasGameRef<SpaceShooterGame> {
  double enemySpeed = 100;
  double spawnInterval = 2.0;
  double elapsedTime = 0;

  // 난이도 증가 설정
  static const double speedIncrement = 10;
  static const double intervalDecrement = 0.1;
  static const double difficultyUpTime = 10.0;  // 10초마다 난이도 증가

  @override
  void update(double dt) {
    elapsedTime += dt;

    // 일정 시간마다 난이도 증가
    if (elapsedTime >= difficultyUpTime) {
      increaseDifficulty();
      elapsedTime = 0;
    }
  }

  void increaseDifficulty() {
    enemySpeed += speedIncrement;
    spawnInterval = (spawnInterval - intervalDecrement).clamp(0.5, 2.0);

    // 난이도 증가 알림 표시
    gameRef.showMessage('Level Up!');
  }

  Enemy createEnemy() {
    return Enemy(speed: enemySpeed);
  }
}

김개발 씨는 친구의 피드백에 고민에 빠집니다. 처음부터 어렵게 만들면 초보자가 포기할 것이고, 처음부터 끝까지 쉬우면 금방 질릴 것입니다.

"난이도를 어떻게 조절해야 할까?" 박시니어 씨가 조언합니다. "좋은 게임은 플로우 상태를 유지시켜.

너무 쉽지도, 너무 어렵지도 않은 상태 말이야. 시간이 지날수록 조금씩 어려워지면 돼." 그렇다면 난이도 조절이란 무엇일까요?

쉽게 비유하자면, 난이도 조절은 마치 운동 트레이너와 같습니다. 처음에는 가벼운 무게로 시작해서 근육이 적응하면 점점 무게를 늘립니다.

갑자기 무거운 것을 들면 다치지만, 적절히 증가시키면 계속 성장할 수 있습니다. 위 코드에서 DifficultyManager는 게임의 난이도를 관리하는 전담 컴포넌트입니다.

enemySpeed는 적의 이동 속도, spawnInterval은 적이 나타나는 간격을 나타냅니다. update 메서드에서 경과 시간을 추적합니다.

10초가 지날 때마다 increaseDifficulty 메서드가 호출되어 난이도가 올라갑니다. clamp 메서드가 중요합니다.

spawnInterval이 너무 작아지면 적이 쏟아져 나와 불가능한 게임이 됩니다. 최소값을 0.5초로 제한하여 아무리 어려워져도 클리어 가능한 수준을 유지합니다.

실제 현업에서는 더 정교한 난이도 시스템을 사용합니다. 플레이어의 실력을 분석하여 동적으로 난이도를 조절하는 DDA(Dynamic Difficulty Adjustment) 시스템도 있습니다.

주의할 점은 난이도 증가 속도입니다. 너무 빨리 올리면 플레이어가 적응할 시간이 없고, 너무 느리면 지루해집니다.

테스트를 통해 적절한 값을 찾아야 합니다. 난이도가 올라갈 때 "Level Up!" 메시지를 보여주는 것도 좋은 아이디어입니다.

플레이어에게 성취감을 주고, 앞으로 더 어려워질 것을 예고합니다. 김개발 씨는 난이도 시스템을 적용한 후 다시 친구에게 보여줍니다.

이번에는 친구가 열심히 플레이합니다. "오, 점점 빨라지네?

중독성 있다!"

실전 팁

💡 - 난이도 증가 시 시각적 피드백(화면 색상 변화 등)을 주면 효과적입니다.

  • 최대 난이도에 상한선을 두어 불가능한 게임이 되지 않게 하세요.

6. 최종 빌드 및 테스트

모든 기능을 구현한 김개발 씨는 드디어 게임을 완성했습니다. 하지만 아직 끝이 아닙니다.

개발 환경에서 잘 동작한다고 해서 실제 기기에서도 잘 동작하리라는 보장은 없습니다. 최종 빌드와 테스트 단계가 남았습니다.

최종 빌드는 게임을 세상에 내보내는 과정입니다. 마치 요리사가 음식을 손님 앞에 내놓기 전에 마지막 간을 보고 플레이팅하는 것처럼, 개발자도 빌드 전에 최적화하고 버그를 잡아야 합니다.

다음 코드를 살펴봅시다.

// 게임 최적화를 위한 오브젝트 풀링
class BulletPool extends Component with HasGameRef<SpaceShooterGame> {
  final List<Bullet> _pool = [];
  static const int poolSize = 20;

  @override
  Future<void> onLoad() async {
    // 미리 총알 객체를 생성해둡니다
    for (int i = 0; i < poolSize; i++) {
      final bullet = Bullet()..removeFromParent();
      _pool.add(bullet);
    }
  }

  Bullet getBullet() {
    if (_pool.isNotEmpty) {
      return _pool.removeLast();
    }
    return Bullet();  // 풀이 비었으면 새로 생성
  }

  void returnBullet(Bullet bullet) {
    bullet.removeFromParent();
    _pool.add(bullet);
  }
}

// 빌드 전 체크리스트 실행
void preBuildChecklist() {
  // 1. 디버그 코드 제거 확인
  assert(() { debugPrint('Debug mode'); return true; }());

  // 2. 하드코딩된 값 상수화 확인
  // 3. 메모리 누수 체크 (removeFromParent 호출 확인)
  // 4. 다양한 화면 크기 테스트
}

김개발 씨는 게임이 완성되었다고 생각했습니다. 개발용 에뮬레이터에서는 완벽하게 돌아갔으니까요.

하지만 박시니어 씨가 경고합니다. "실제 기기에서 테스트해봤어?

에뮬레이터랑 다를 수 있어." 김개발 씨는 자신의 휴대폰에 앱을 설치해봅니다. 충격적이게도 게임이 버벅거립니다.

"왜 이러지? 에뮬레이터에서는 잘 됐는데..." 그렇다면 최적화란 무엇일까요?

쉽게 비유하자면, 최적화는 마치 짐을 싸는 것과 같습니다. 여행 갈 때 필요 없는 짐까지 다 가져가면 무겁고 불편합니다.

필요한 것만 효율적으로 싸야 합니다. 게임도 마찬가지로 불필요한 연산과 메모리 사용을 줄여야 합니다.

위 코드의 오브젝트 풀링은 대표적인 최적화 기법입니다. 총알을 쏠 때마다 새로운 객체를 생성하면 가비지 컬렉션이 자주 발생하여 버벅거립니다.

미리 객체를 만들어두고 재사용하면 이 문제를 해결할 수 있습니다. getBullet 메서드는 풀에서 총알을 꺼내오고, returnBullet 메서드는 사용이 끝난 총알을 풀에 되돌립니다.

이렇게 하면 객체 생성 비용을 크게 줄일 수 있습니다. 빌드 전에는 반드시 체크리스트를 확인해야 합니다.

디버그용 코드가 남아있지 않은지, 하드코딩된 값은 없는지, 메모리 누수는 없는지 점검합니다. 다양한 화면 크기에서 테스트하는 것도 중요합니다.

태블릿에서는 잘 보이는데 작은 폰에서는 UI가 겹칠 수 있습니다. 실제 현업에서는 QA 팀이 별도로 있어서 다양한 기기와 시나리오에서 테스트합니다.

개인 프로젝트라면 친구나 가족에게 테스트를 부탁하는 것도 좋은 방법입니다. 최종 빌드 명령어는 간단합니다.

Android는 flutter build apk, iOS는 flutter build ios를 실행합니다. 하지만 빌드 전 준비가 더 중요합니다.

김개발 씨는 오브젝트 풀링을 적용하고 여러 번 테스트한 후 드디어 앱 스토어에 게임을 출시합니다. 첫 다운로드가 발생했을 때의 그 기쁨이란!

"내가 만든 게임을 다른 사람이 플레이하고 있어!" 처음 시작했던 그 주말로부터 한 달이 지났습니다. 김개발 씨는 이제 자신 있게 말할 수 있습니다.

"저, 게임 개발자예요."

실전 팁

💡 - 릴리즈 빌드는 디버그 빌드보다 훨씬 빠릅니다. 성능 테스트는 릴리즈 모드에서 하세요.

  • 크래시 리포팅 도구(Firebase Crashlytics 등)를 연동하여 출시 후 버그를 추적하세요.

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

#Flutter#Flame#GameDevelopment#SpriteComponent#CollisionDetection#Flutter,Flame,Game

댓글 (0)

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

함께 보면 좋은 카드 뉴스