• IceCraft - 마피아 게임 페이지 CSS 리팩토링 과정 기록

    2024. 8. 12.

    by. 서카츄

    마피아 게임 개발이 끝나감에 따라, CSS총 점검에 들어갔다.

    수정해야 할 곳이 종종 보여서 CSS 수정작업을 하게 되었다.

     

     

     

    마피아 게임페이지 UI/UX 수정 리스트

    기존 레이아웃 수정 → background 넣고, 조금 게임스럽게 변경해보기, 타이머 맨 위 상단, 가운데로 옮기기, 방 나가기 버튼 수정해보기

    게임 모달창이 너무 크다 → 조금 줄여야함

    폰트사이즈가 너무 작았다 → 16px에서 20px로 키우자

    플레이어들이 전부 레디를 진행했을 때, 방장이 게임시작 버튼이 white로 안보이는 현상 수정 → background 색상 추가

    게임 플레이어들 입장 번호 css 수정 → 기존에는 바깥에 있었는데 UI적으로 화면 안에 보이게 수정

    게임 시작시 직업 선택 카드 깨지는 현상 → width 조절

    프로그래스바 모달창마다 width가 달랐음 → width 통일

    내가 방장인지 아닌지 확인이 안됨 → 사용자 경험을 높이기 위해 방장일 시 방장이라는 이미지 추가하기

    게임을 진행하다보니 밤인지 낮인지 헷갈림 → 밤과 낮에 따라 backgroud 이미지를 변경하며 사용자가 게임에 몰입할 수 있도록 변경

    밤인지 낮인지 타이머에도 UI 이미지 추가 → 타이머 오른쪽에 해, 달 이미지 추가

    게임 투표중 유저를 선택할 때, check 이미지가 살짝 위로 올라가있음 → position으로 다시 조절하기

     

     

     

    적다보니 자잘자잘 수정할 곳이 보였다 크흡...(ㅠㅠ)

    css만 고치는 내용은 금방 끝나서, 코드 변경이 있는 위주로 작성하였다.

    차근차근 수정해보자~!

     

     

     

    1.  마피아 게임페이지 레이아웃 변경 & background 밤, 낮 조건부랜더링 추가하기

    마피아 게임 페이지 기존 레이아웃

     

    이전에 마피아 게임 페이지 레이아웃은 이렇게 생겼었는데 (...)

    보면 볼수록 회의 하는 느낌이라서;;; 게임스럽게 바꿔보기로 했다.

     

     

     

     

    바뀐 레이아웃

    마피아 게임이라는 느낌을 추가하기 위해 뒤에 백그라운드를 변경해 보았다.

    타이머는 왼쪽에서 맨 위 가운데로 옮겼고, 방 나가기 버튼은 기존 빨간색 버튼에서 다르게 바꿔보았다.

    그리고 게임 진행 중 "밤""낮"으로 진행될 때, UI적으로 유저들이 좀 더 쉽게 몰입할 수 있도록 백그라운드를 바꿔보았다.

     

    추가로, 방에 입장하면 게임중인지 아닌지 UI적으로 구분이 안 간다고 생각하여

    현재 타이머는 게임이 시작 하기 전에는 안보이고, 게임이 시작한 후 타이머를 보이게 진행하였다.

     

     

     

    조건에 따라 뒤에 있는 배경을 바꿔보자

    .day {
      background: url("../../assets/images/mafia_room_bg_day.avif") center center/cover no-repeat;
    }
    
    .night {
      background: url("../../assets/images/mafia_room_bg_night.avif") center center/cover no-repeat;
    }

     

    먼저 css에 바꿀 이미지들을 추가한다.

    day와 night라는 클래스명을 추가하였다.

     

     

     

     

    마피아 게임페이지로 가서 조건부를 만들어보자

      //NOTE - 밤, 낮 배경
      useEffect(() => {
        if (isDay === "낮") {
          setMorning(true);
          setNight(false);
          return;
        }
    
        if (isDay === "밤") {
          setNight(true);
          setMorning(false);
        }
      }, [isDay]);

     

    우리는 서버와 소켓으로 통신하고 있어서, 회의때 공통모달은 "showModal"이라는 이름으로 통일하기로 했는데,

    밤이 되었는지 낮이 되었는지 서버에서 따로 구분해주는 내용이 없어서 includes를 사용하게 되었다.

    서버에서 "낮"이라는 키워드가 들어오면, 상태변경으로 setDay를 true로 바꿔주고,

    서버에서 "밤"이라는 키워드가 들어오면, 상태변경으로 setNight를 true로 바꿔주었다.

    useEffect 훅에서 isDay라는 내용이 들어오면, 조건부로 "밤"인지 "낮"인지 구분하여

    상태변경으로 true, false 등으로 변경해준다.

     

     

     

     

     

      //NOTE -  테마
      const dayTime = morning ? S.day : "";
      const nightTime = night ? S.night : "";
      const resultClassName = `${dayTime} ${nightTime}`;

     

    그리고 변수에 별도로 넣어주었는데, 그 이유는

    dayTime이라는 변수가 들어왔을 때, nightTime 변수에 있는 night 클래스네임이 비활성화 되어야하고 (없어져야 하고)

    반대로 nightTime 변수가 들어오면, morning 클래스네임이 비활성화 되어야하고 night 클래스네임이 활성화 되어야 하는데

    (결국엔 toggle과 비슷한 느낌..?)

    그 내용을 태그에 삼항연산자로 쓰게되면 코드가 길어지고, 가독성이 떨어져 보여서

    팀원들이 보았을 때 직관적으로 알기 쉽게 볼 수 있도록 변수에 넣어줘서 관리하게 되었다.

     

     

     

     

     <div className={`${S.roomBackground} ${resultClassName}`}>
     	** 중략 **
     </div>

     

    그리고 배경화면을 바꾸는 div태그에 resultClassName 변수를 넣어주었다!

     

     

     

     

     

     

    놓치지 않고 방 입장시 초기화 & 게임 종료 시 초기화도 진행해준다!

      //NOTE - 방 입장 시 초기화
      useEffect(() => {
        console.log("🚀 MafiaPlayRooms: 방 입장 시 초기화");
        setOverlayReset(); //Local,Remote 클릭 이벤트 및 캠 이미지 초기화
        setModalReset(); //전체 모달 요소 초기화
        setGameReset(); // 죽은 players 및 게임 state 초기화
        setDay(false);
        setNight(false);
      }, []);
      
        //NOTE - 게임 종료
      useEffect(() => {
        if (isGameState === "gameEnd") {
          console.log("🚀 isGameState:", isGameState);
          setOverlayReset(); //Local,Remote 클릭 이벤트 및 캠 이미지 초기화
          setModalReset(); //전체 모달 요소 초기화
          setGameReset(); // 죽은 players 및 게임 state 초기화
          setIsMediaReset(true); // 캠 및 오디오 초기화
          setDay(false);
          setNight(false);
        }
      }, [isGameState]);

     

    + error 처리시 false로 바꿔주었다

     

     

     

     

    밤이 되었을때 바뀌는 배경

     

     

    밤에서 낮으로 바뀔때 변하는 배경

     

    밤에도, 낮에도 잘 작동되는거 확인완료✨

     

     

     

     


    2.  타이머에 밤, 낮 UI 이미지 추가

    마피아 게임페이지에서 배경화면만 변경하니 아쉬워서(...)

    타이머 오른쪽에 밤과 낮일때도 해,달 이미지를 추가해보기로 했다.

    앞서 배경화면과 같은 내용으로 진행하였다.

     

    추가한 UI

        <div className={S.gameTimer}>
              <SpeakTimer />
              <p className={S.dayAndNight}>
                <span className={S.sun}>{morning && <Image src={SunIcon} className={S.sunImage} alt="sun icon" />}</span>
                <span className={S.moon}>{night && <Image src={MoonIcon} className={S.moonImage} alt="moon icon" />}</span>
              </p>
    	</div>

     

    타이머 오른쪽에 이미지를 추가해 주었다.

    이제 조건에 따라 morning이 들어오면 "해" 이미지가 나오고

    night가 들어오면 "달" 이미지가 나온다.

    앞서 내용과 코드를 같이 쓰고 있기 때문에 코드는 생략!

     

     

     

     

     

     

    밤으로 진입시는 달 이미지를

    낮으로 진입하면 해 이미지가 잘 적용되는 것을 확인할 수 있다✨

    + 애니메이션 요소는 덤!

     

     

     

     

     

    3.  플레이어 중 방장인 플레이어에게 이미지 추가

    이 부분은 기존에 내가 개인과제로 했었던 왕관 이미지를 재활용하게 되었다 (^^)

    먼저 local 컴포넌트와 remote 컴포넌트에 왕관 이미지를 추가해주었다.

    <div className={S.chief}>
        <Image src={ChiefImage} alt={localParticipant.identity} />
    </div>

     

    이미지를 추가하고

    해당 플레이어가 방장일 경우, 해당 사용자만 왕관 이미지를 보여줄 예정이다.

     

     

     

     

     

    오른쪽 위에 방장이라는 이미지를 추가해 주었다.

     

     

     

     

    실시간 방장 정보를 받아오자

    서버에서는 이미 사용자가 방장인지 아닌지 여부를 보내주고 있다.

    먼저 로컬과 리모트 컴포넌트에 지역스테이트로 상태관리를 추가해준다.

    기본값은 false로 만들어준다.

     const [isChief, setIsChief] = useState(false);

     

     

     

     

    그리고 useEffect 훅 안에 방장의 플레이어 id와 게임 상태가 update 되면 정보가 바뀔 때 마다 실행된다.

    로컬에 있는 사용자 컴포넌트

      //NOTE - 게임 시작 전) 실시간 방장 정보 update
      useEffect(() => {
        if (!localParticipant.localParticipant.identity || !chiefPlayerId) {
          return;
        }
        const localPlayerId = localParticipant.localParticipant.identity;
    
        if (isGameState === "gameReady" && localPlayerId === chiefPlayerId.chief) {
          setIsChief(true);
        }
    
        if (isGameState === "gameStart" || isGameState === "gameEnd") {
          setIsChief(false);
        }
      }, [chiefPlayerId, isGameState]);

     

    코드 설명

    local에 있는 사용자 id, 또는 방장 id가 존재하지 않으면 useEffect 훅의 실행을 중지한다 (return)

    두 값이 모두 유효하지 않으면 이후의 코드를 실행하지 않는다.

     

    isGameState(게임상태)가 "gameReady" 고, 로컬id와 방장id가 같으면

    상태변경을 true로 바꿔준다.

    즉, 게임이 시작 되기 전 준비 상태에서 로컬 플레이어가 방장이면 방장상태를 나타낸다.

     

    isGameState(게임상태)가 "gameStart" 또는 게임 상태가 "gameEnd" 면

    상태변경을 false로 바꿔준다. 즉, 게임이 시작되거나 종료되면 방장 이미지를 표시해주지 않는다.

     

     

     

    리모트 사용자 컴포넌트

      //NOTE - 게임 시작 전) 실시간 방장 정보 update
      useEffect(() => {
        if (isGameState === "gameReady" && remote.participant.identity === chiefPlayerId.chief) {
          setIsChief(true);
        }
    
        if (isGameState === "gameStart" || isGameState === "gameEnd") {
          setIsChief(false);
        }
      }, [chiefPlayerId, remote, isGameState]);

     

    코드 설명

    게임 상태가 "gameReady", 그리고 리모트 사용자의 id와 방장 아이디가 같으면

    방장 상태변경을 true로 바꿔준다.

     

    게임 상태가 "gameStart" 또는 게임상태가 "gameEnd" 면

    방장 상태변경을 false로 바꿔준다

     

     

     

    return부분(공통)

    {isChief && <Image src={ChiefImage} alt={localPlayerId} />}

     

    그리고 로컬 사용자 컴포넌트, 리모트 사용자 컴포넌트에서

    isChief가 true일 경우 UI를 랜더링해서 보여준다.

     

     

     

     

    그리고 로컬에 있는 사용자와, 리모트에 있는 사용자들에 대해 데이터 상태 공유를 해야하니

    사용자 정보에 관한 데이터를 전역관리로 만들어준다.

      updateRoomInfo: { chief: "", roomId: "" },
      
      actions: {
          setChiefPlayerId: (newChief) =>
          set((state) => {
            const presentRoomId = state.presentRoomId;
            const updateRoomInfo = state.updateRoomInfo;
    
            // 현재 방과 업데이트된 방의 유효성 검사
            if (presentRoomId !== newChief.roomId) {
              return { updateRoomInfo };
            }
    
            // 현재 방장과 업데이트된 방장의 유효성 검사
            if (updateRoomInfo.chief === newChief.chief) {
              return { updateRoomInfo };
            }
    
            // 새로운 방장 업데이트
            if (updateRoomInfo.chief !== newChief.chief) {
              return { updateRoomInfo: newChief };
            }
    
            return { updateRoomInfo };
          }),
      
      }
      
      export const useChiefPlayer = () => useGameStore((state) => state.updateRoomInfo);

     

    그럼 각각 로컬 컴포넌트와, 리모트 컴포넌트에서 해당 내용을 가져와서 사용한다.

     

     

     

     

    후기

    배포하고 나서도 UI/UX적으로 어떨지 계속 고민하면서 수정중인데,

    무엇을 하더라도 완벽하게 만들기는 어려운 것 같다 ㅜㅜ

    배포하고 나서도 계속 신경쓰이는 것들이 계속 보였고

    이번 프로젝트를 하면서 느꼈던 점은

    오류가 없게 만드는 것 보다는 그에 대처하는 자세가 중요하다는 것을 깨달았다.

     

    끝~ !

    댓글