Flutter - Defense Game - 1

김진한·2023년 1월 27일
3

Flutter

목록 보기
10/11

최근 Flutter 3.7 업데이트에서 Flutter의 성능이 개선되고 3D 렌더링 엔진이 추가 된다는 것을 알게됐습니다. 그래서 이번 기회에 Flutter Game을 더 공부해보고자 Flame을 사용해서 간단한 디펜스 게임을 만들어 보기로 했습니다.
코드가 꽤 길어질 것이기 때문에 여러 포스팅으로 나눠서 진행하겠습니다.



이번 포스팅에서는 Flame을 이용해 격자모양 배경을 만들어 보겠습니다.



Flame

Flame 엔진을 사용해서 만들 것이기 때문에 pubspec.yaml 에서 Flame을 추가해주세요.
버전은 1.0.0을 사용하겠습니다.

dependencies:
  flutter:
    sdk: flutter

  flame: ^1.0.0

https://pub.dev/packages/flame



main.dart

비동기 함수 호출을 위해서 ensureInitialized()을 먼저 호출해줍니다.
그리고 화면을 전부 사용하면서, 화면 방향은 세로로 고정시켰습니다.
일반적인 앱에서는 runApp 함수에 MaterialApp을 호출하는데, 대신 GameWidget을 호출하겠습니다.
Flutter에서는 모든 것이 위젯인 만큼 GameWidget 또한 statefulwidget을 상속받고 있습니다. GameWidget은 추상클래스인 Game을 상속받은 객체를 제너릭으로 선언해줘야 합니다. Game 클래스를 상속받은 FlameGame을 타입으로 선언 해줍니다.

import 'package:defense_tutorial/defense_game.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flutter/services.dart';

void main() async{

  /// 비동기 함수 호출 위해 먼저 호출 	
  WidgetsFlutterBinding.ensureInitialized();
  
  /// Flame이 모든 화면 전부를 사용하도록 설정 
  await Flame.device.fullScreen();
  
  /// 화면은 세로로 고정 
  await Flame.device.setOrientation(DeviceOrientation.portraitUp);
  runApp(GameWidget<FlameGame>(game: DefenseGame()));
}


DefenseGame widget

GameWidget에 넣어줄 DefenseGame 위젯이 필요합니다. FlameGame을 상속받았고, onLoad 함수를 구현하고 있습니다. 여기서 MapComponent라는 것을 생성해서 FlameGame에 add를 해주고 있습니다.

import 'dart:async';

import 'package:defense_tutorial/map/map_component.dart';
import 'package:flame/game.dart';

class DefenseGame extends FlameGame {
  
  FutureOr<void> onLoad() async {
    var mapComponent = MapComponent(
      tileSize: Vector2(80, 80),
      mapGrid: Vector2(5, 5),
      position: Vector2(0, 0),
      size: Vector2(500, 500),
    );
    add(mapComponent);
    return null;
  }
}


MapComponent, MapTile

MapTile은 정사각형 한개의 Component이고, MapComponent는 MapTile을 여러개로 이어붙이는 Component입니다. 두 클래스 모두 PositionComponent를 상속받고 있습니다. Flame에서 화면에 보여주는 거의 대부분의 것들은 Flame에서 제공하는 Component들을 상속받은 클래스들입니다. 그래야 FlameGame에 add를 하고, 화면에 보여줄 수 있기 때문입니다.

MapComponent

import 'dart:async';

import 'package:defense_tutorial/map/map_tile.dart';
import 'package:flame/components.dart';

class MapComponent extends PositionComponent {
  late Vector2 tileSize;
  late Vector2 mapGrid;

  MapComponent({
    required this.tileSize,
    required this.mapGrid,
    position,
    size,
  }) : super(position: position, size: size);

  final double _strokeWidth = 5;

  
  FutureOr<void> onLoad() async {
    await super.onLoad();
    for (var w = 0; w < mapGrid.x; w++) {
      for (var h = 0; h < mapGrid.y; h++) {
        var mapTile = MapTile(
            position: _calcPosition(w, h, tileSize.x, tileSize.y, _strokeWidth),
            size: tileSize,
            strokeWidth: _strokeWidth);
        add(mapTile);
      }
    }
    return null;
  }

  /// MapTile이 그려질 때 paintstroke 값이 4라면 가장 왼쪽과 가장 오른쪽이 2씩 짤린다.
  /// 때문에 strokeWidth의 절반 값 만큼 더해줘야 한다.
  Vector2 _calcPosition(int w, int h, double x, double y, double strokeWidth) {
    return Vector2(
      (w * tileSize.x) + (strokeWidth / 2),
      (h * tileSize.y) + (strokeWidth / 2),
    );
  }
}

MapTile

MapTile에서는 render 함수를 구현해서 canvas를 사용하고 있습니다.

import 'package:flame/components.dart';
import 'package:flutter/material.dart';

class MapTile extends PositionComponent {
  MapTile({
    required Vector2 position,
    required Vector2 size,
    required this.strokeWidth,
  }) : super(position: position, size: size);
  final double strokeWidth;

  
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawRect(
        size.toRect(),
        Paint()
          ..style = PaintingStyle.stroke
          ..strokeWidth = strokeWidth
          ..color = Colors.green);
  }

}



이번 포스팅에서는 격자배경을 그려봤고, 다음 포스팅에서 이어서 개발 진행하겠습니다.


파일 구조

프로젝트 링크
https://github.com/jinhan38/defense_tutorial

0개의 댓글