Development/Dart&Flutter

[Flame] Klondlike 2 - 카드 렌더링 구현

사이바 미도리 2024. 9. 7. 12:07

 

Card 구조체

  • 트럼프 Card는
    • 앞/뒷면 bool 변수가 있어야 함.
    • rank가 있어야 함(숫자)
    • suit가 있어야 함(도형)
  • 각 구현은 enum 또는 무지성 int로 할 수 있음.
    • rank는 대소비교가 필요하므로, int가 좋을듯.
    • 도형은 대소관계가 없으므로, suit가 좋을듯.
      • 이 게임에서는 도형의 서열은 없으니까.
      • 단지 도형이 같은지 여부만 확인하면 됨.

 

Suit 구현

  • 부모없는 immutable로 선언.
    • immutable: 인스턴스화 이후에 어떠한 멤버변수도 바꿀 수 없음
  • Factory 생성자 만들자. singleton 쓰려고.
    • 매번 object를 생성하지 말고, 매번 pre-build object를 반환할 뿐이라 성능적으로 우수하다.
    • Suit.fromInt라는 factory를 정의하였다.
  factory Suit.fromInt(int index) {
    assert(index >= 0 && index <= 3);
    return _singletons[index];
  }
  • factory는 내부적으로 private constructor만 사용토록 하였다. privateConstructor는 아래와 같이 짠다.
  Suit._(this.value, this.label, double x, double y, double w, double h)
      : sprite = klondikeSprite(x, y, w, h);

  final int value;
  final String label;
  final Sprite sprite;

 

  • _singleton 리스트는 4개의 entry를 가진다.
  static final List<Suit> _singletons = [
    Suit._(0, '♥', 1176, 17, 172, 183),
    Suit._(1, '♦', 973, 14, 177, 182),
    Suit._(2, '♣', 974, 226, 184, 172),
    Suit._(3, '♠', 1178, 220, 176, 182),
  ];

spritecow

  • png에서, sprite들의 위치를 찾아내는 사이트

  • 알아낸 각 도형의 width, height를 Suit._에 입력하자.

Rank 구현

  • 마찬가지로, 숫자에 대해서도 width, height를 알아내서 아래와 같이 입력하자.
import 'package:flame/components.dart';
import 'package:flame/flame.dart';
import 'package:flutter/foundation.dart';

@immutable
class Rank {
  factory Rank.fromInt(int value) {
    assert(value >= 1 && value <= 13);
    return _singletons[value - 1];
  }

  Rank._(
    this.value,
    this.label,
    double x1,
    double y1,
    double x2,
    double y2,
    double w,
    double h,
  )   : redSprite = klondikeSprite(x1, y1, w, h),
        blackSprite = klondikeSprite(x2, y2, w, h);

  final int value;
  final String label;
  final Sprite redSprite;
  final Sprite blackSprite;

  static final List<Rank> _singletons = [
    Rank._(1, 'A', 335, 164, 789, 161, 120, 129),
    Rank._(2, '2', 20, 19, 15, 322, 83, 125),
    Rank._(3, '3', 122, 19, 117, 322, 80, 127),
    Rank._(4, '4', 213, 12, 208, 315, 93, 132),
    Rank._(5, '5', 314, 21, 309, 324, 85, 125),
    Rank._(6, '6', 419, 17, 414, 320, 84, 129),
    Rank._(7, '7', 509, 21, 505, 324, 92, 128),
    Rank._(8, '8', 612, 19, 607, 322, 78, 127),
    Rank._(9, '9', 709, 19, 704, 322, 84, 130),
    Rank._(10, '10', 810, 20, 805, 322, 137, 127),
    Rank._(11, 'J', 15, 170, 469, 167, 56, 126),
    Rank._(12, 'Q', 92, 168, 547, 165, 132, 128),
    Rank._(13, 'K', 243, 170, 696, 167, 92, 123),
  ];
}

드디어 Card component 구현시작

일단 뒷면을 구현하자

  • 처음에는 faceup을 false 로.
  Card(int intRank, int intSuit)
      : rank = Rank.fromInt(intRank),
        suit = Suit.fromInt(intSuit),
        _faceUp = false,
        super(size: KlondikeGame.cardSize);

  final Rank rank;
  final Suit suit;
  bool _faceUp;
  • _faceup은 private 변수이므로, get 함수 추가.
  bool get isFaceUp => _faceUp;
  bool get isFaceDown => !_faceUp;
  void flip() => _faceUp = !_faceUp;

 

  • 디버그용 toString 추가
  @override
  String toString() => rank.label + suit.label; // e.g. "Q♠" or "10♦"

 

 

class Card extends PositionComponent {
  Card(int intRank, int intSuit)
      : rank = Rank.fromInt(intRank),
        suit = Suit.fromInt(intSuit),
        _faceUp = false,
        super(size: KlondikeGame.cardSize);

  final Rank rank;
  final Suit suit;
  bool _faceUp;

  bool get isFaceUp => _faceUp;
  void flip() => _faceUp = !_faceUp;
  
  
  @override
  void render(Canvas canvas) {
    if (_faceUp) {
      _renderFront(canvas);
    } else {
      _renderBack(canvas);
    }
  }



  void _renderBack(Canvas canvas) {
    canvas.drawRRect(cardRRect, backBackgroundPaint);
    canvas.drawRRect(cardRRect, backBorderPaint1);
    canvas.drawRRect(backRRectInner, backBorderPaint2);
    flameSprite.render(canvas, position: size / 2, anchor: Anchor.center);
  }
  
  
  static final Paint backBackgroundPaint = Paint()
    ..color = const Color(0xff380c02);
  static final Paint backBorderPaint1 = Paint()
    ..color = const Color(0xffdbaf58)
    ..style = PaintingStyle.stroke
    ..strokeWidth = 10;
  static final Paint backBorderPaint2 = Paint()
    ..color = const Color(0x5CEF971B)
    ..style = PaintingStyle.stroke
    ..strokeWidth = 35;
  static final RRect cardRRect = RRect.fromRectAndRadius(
    KlondikeGame.cardSize.toRect(),
    const Radius.circular(KlondikeGame.cardRadius),
  );
  static final RRect backRRectInner = cardRRect.deflate(40);
  static final Sprite flameSprite = klondikeSprite(1367, 6, 357, 501);