커스텀 위젯

해당 포스트에는 커스텀 위젯에 대한 내용을 주로 다룹니다. 만약 커스텀 위젯에 대해서 잘 모른다면 아래 강의를 보고 커스텀 위젯을 이해하고 와주십시오.

커스텀 위젯 강의

✨게임 페이지 미리보기


게임페이지는 다음 화면과 같이 구현할 것입니다.
왼쪽 공간에는 유저들의 정보를 나타내는 공간이며 Column형태로 정렬되어 2명에서 최대 4명까지 설정 가능합니다.

중앙 공간에는 건물 카드들과 자신의 건물 상태들을 알아볼 수 있는 공간이 Column형태로 정렬됩니다. 건물 카드들은 GridView를 사용해서 5X3형태로 나타냅니다.

마지막으로 오른쪽 공간에는 주사위와 관련된 기능들을 구현할 것입니다. 주사위가 위로 떠올랐다가 다시 아래로 떨어지는 애니매이션을 구현할 공간과 주사위 결과, 주사위를 굴리는 버튼을 Column형태로 정렬합니다.

🔍게임페이지 구성


게임페이지의 구성입니다. 우선 Scaffoldbody안에 Row를 사용해서 플레이어 영역, 게임 보드판 영역, 주사위 영역으로 나눕니다.

플레이어 영역은 커스텀 위젯인 _playerWidget을 List로 만들고 Column을 사용해 배치하여 구현했습니다.

게임 보드판 영역은 GridView를 이용하여 카드를 가로 5개, 세로 3개로 배치하고 CardStack이라는 커스텀 위젯을 이용하여 카드가 겹쳐보이게 만들어서 몇장이 남아있는지 알아 볼 수 있도록 만듭니다. GridView아래에는 플레이어 본인의 현재 상태에 대한 정보들을 표현합니다. 미니빌의 승리조건인 주요 건물 현황과 현재 가진 돈을 표시하고 구매한 건물 내역과 턴을 넘길 수 있는 버튼이 존재합니다.

게임 보드판을 Stack위젯으로 크게 묶은 이유는 해당 화면처럼 카드를 선택했을때 카드 강조와 카드를 구매하기 위한 버튼을 표시하기 위함입니다. 카드 선택과 관련된 내용은 다음 포스팅에서 다루도록 하겠습니다.

주사위 영역은 AnimatedBuilder를 이용해서 간단한 주사위 애니매이션을 구현할 영역과 그 아래에 주사위 결과, 주사위 굴리기 버튼을 배치하였습니다.

해당 움짤처럼 주사위가 위로 떠올랐다가 떨어지며 주사위 이미지가 무작위로 1~6까지 빠르게 바뀌다가 애니매이션이 끝나면 결과를 나타나도록 애니매이션을 구현합니다. 애니매이션에 대한 내용은 다음 포스팅에서 다루도록 하겠습니다.

📷이미지 정보

카드 이미지는 모두 800X1400의 사이즈로 만들어졌고 주사위 이미지는 1024X1024의 사이즈로 만들어졌습니다. 본 포스팅에서 사용되는 이미지는 모두 무료 버전의 미리캔버스로 제작하였으니 참고해주십시오.

그리고 이미지를 프로젝트에 추가하는 방법을 알려드리겠습니다. 위 이미지를 참고하여 위치에 맞게 assets폴더를 만듭니다.

assets폴더를 만들었다면 이미지 종류별로 폴더를 만들어서 이미지를 정리하여 저장해줍니다.

마지막으로 pubspec.yaml 파일에 다음과 같이 코드를 작성해주어야합니다. 여기서 주의해야할 점은 pubspec.yaml 파일은 파이썬처럼 들여쓰기에 매우 신경을 써주셔야 오류없이 잘 돌아갑니다.

💻레이아웃 코드 구조

class GameScreen extends StatefulWidget {
  final int numOfPlayers;
  final bool createJoin;
  final String roomCode;
  final String userName;

  GameScreen(
      {required this.numOfPlayers,
      required this.createJoin,
      required this.roomCode,
      required this.userName});

  
  _GameScreenState createState() => _GameScreenState();
}

class _GameScreenState extends State<GameScreen>
    with SingleTickerProviderStateMixin, WidgetsBindingObserver {
 
  
  void initState() {
    super.initState();
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
    ...
  }

  
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width;
    double sideSpaceWidth = screenWidth * 0.2;

    ...

    return Scaffold(
        body: Row(
          children: [
            //게임스크린 (플레이어 영역)
            Container(
              ...
            ),
            //게임스크린 (게임 보드 영역)
            Expanded(
                ...
            ),
            //게임스크린 (주사위 영역)
            Container(
              ...
            ),
          ],
        ),
      );
  }
  // 커스텀 위젯 생성
  ...
}

레이아웃의 전체적인 구조는 이렇게 생겼습니다. 여기서 SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); 코드는 상단바와 하단바를 없애고 게임을 전체적인 화면에서 플레이 할 수 있도록 해주는 역할을 합니다.

MainScreen으로부터 값 받아오기

class GameScreen extends StatefulWidget {
  final int numOfPlayers;
  final bool createJoin;
  final String roomCode;
  final String userName;

  GameScreen(
      {required this.numOfPlayers,
      required this.createJoin,
      required this.roomCode,
      required this.userName});

  
  _GameScreenState createState() => _GameScreenState();
}

화면 전환을 할 때 위젯간에 값을 주고 받으려면 받는쪽에서 다음과 같이 생성자를 만들어줘야 합니다. 값을 보내는 MainScreen코드에 대해 궁금하면 이전 포스팅을 참고하여 주십시오.
레이아웃 구성에 필요한 값은 numOfPlayers입니다. numOfPlayers는 방의 인원수로 왼쪽 플레이어 영역에서 몇 개의 플레이어 위젯을 만들어낼지 결정합니다.

📐플레이어 영역 만들기

class _GameScreenState extends State<GameScreen>
    with SingleTickerProviderStateMixin, WidgetsBindingObserver {
    
	double screenWidth = MediaQuery.of(context).size.width;
    double sideSpaceWidth = screenWidth * 0.2;
   

    List<Widget> playerWidgets = [];
    // numOfPlayers 만큼 왼쪽 플레이어 영역 추가
    for (int i = 0; i < widget.numOfPlayers; i++) {
      playerWidgets.add(
        Flexible(
            child: _playerWidget(
                "player $i")),
      );
    }
    // 4 - numOfPlayers 만큼 빈공간 생성
    for (int i = 0; i < (4 - widget.numOfPlayers); i++) {
      playerWidgets.add(
        Flexible(
          child: Padding(padding: EdgeInsets.all(5.0)),
        ),
      );
    }
    return Scaffold(
      body: Row(
      children: [
          //게임스크린 (플레이어 영역)
          Container(
            width: sideSpaceWidth,
              color: Colors.grey[200],
              child: Column(
                children: playerWidgets,
              ),
          ),
          //게임스크린 (게임 보드 영역)
          Expanded(
              ...
          ),
          //게임스크린 (주사위 영역)
          Container(
            ...
          ),
        ],
      ),
    );
         
    Widget _playerWidget(String playerName) {
      return Container(
        padding: EdgeInsets.all(5.0),
        child: Column(
          children: [
            // 프로필 아이콘과 이름
            Expanded(
              flex: 1,
              child: Row(
                children: [
                  Expanded(
                    flex: 2,
                    child: AutoSizeText(
                      playerName,
                      minFontSize: 5,
                      style: TextStyle(fontSize: 20.0), // 시작할 폰트 크기
                      maxLines: 1, // 최대 줄 수
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: SizedBox(),
                  ),
                  Expanded(
                    flex: 1,
                    child: AutoSizeText(
                      '3원',
                      minFontSize: 5,
                      style: TextStyle(fontSize: 20.0), // 시작할 폰트 크기
                      maxLines: 1, // 최대 줄 수
                    ),
                  ),
                  Expanded(
                    flex: 1,
                    child: ElevatedButton(
                      onPressed: () {
                       // 건물 확인 기능
                      },
                      child: FittedBox(
                        fit: BoxFit.cover,
                        child: Icon(Icons.check, size: 100),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            // 4장의 카드
            Expanded(
              flex: 2,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Expanded(
                    flex: 1,
                    child: Image.asset(...),
                  ),
                  Expanded(
                    flex: 1,
                    child: Image.asset(...),
                  ),
                  Expanded(
                    flex: 1,
                    child: Image.asset(...),
                  ),
                  Expanded(
                    flex: 1,
                    child: Image.asset(...),
                  ),
                ],
              ),
            ),
          ],
        ),
      );
    }
}

먼저 플레이어 영역, 보드판 영역, 주사위 영역을 가로로 구분하기 위해 기기 전체의 가로폭 값을 구한뒤 플레이어 영역과 주사위 영역을 각각 기기 가로폭의 20%만큼 차지하도록 만들어줍니다.

double screenWidth = MediaQuery.of(context).size.width;
double sideSpaceWidth = screenWidth * 0.2;
그리고 표시할 플레이어 위젯을 커스텀 위젯으로 만든 뒤에 플레이어 위젯을 List형태로 저장하여 List를 Column으로 배치하여 플레이어 영역을 구현해줍니다.


주의해야할 점은 Flexible형태로 위젯을 리스트에 저장하기 때문에 플레이어 위젯을 일정한 크기로 배치하려면 인원수에 상관없이 List에 최대 인원수만큼 위젯을 저장해야 합니다.

고로 커스텀 위젯으로 만든 _playerWidgetnumOfPlayers수 만큼 List에 저장하고 4-numOfPlayer수 만큼 빈 위젯을 추가해줘야 합니다. 그래야 해당 사진처럼 인원수에 상관없이 일정한 크기로 플레이어 위젯이 배치됩니다.

현재는 플레이어의 이름, 가진 돈, 건물 현황, 주요건물 상황, 턴 상황(빨간색 테두리)에 대한 내용을 적용하지 않았습니다. 후에 게임 로직 구현하는 과정에서 플레이어 위젯에 대한 내용을 다시 보여드릴테니 걱정하지 말고 따라와주십시오😎

profile
백석대학교 소프트웨어학과 4학년 재학중

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN