어몽어스 - 게임 규칙 설정 기능

이도희·2022년 1월 22일
0

Among Us 프로젝트

목록 보기
10/10
post-thumbnail

본 포스트는 베르님의 Make the 어몽어스를 정리한 포스트입니다.
https://www.youtube.com/watch?v=vq3_p4XlXrI&list=PLYQHfkihy4Aw6QjsZqwwbD4ihpwvm7N0U&index=10

이번 시간에는 게임 규칙을 설정하는 기능을 구현하겠습니다.

1. 방의 최대, 최소 인원 / 임포스터 수 설정

먼저 MainMenuScene에서 방을 만들면서 선택한 인원 수와 임포스터의 수를 AmongUsRoomManger에 설정하겠습니다. 먼저 AmongUsRoomManager에 다음의 변수들을 선언합니다.

public class AmongUsRoomManager : NetworkRoomManager
{
    public int minPlayerCount;
    public int imposterCount;
    .
    .
}

그 다음 Online UI > Scripts > CreateRoomUI 스크립트를 열어준 후 다음 내용을 수정합니다.

  1. NetworkRoomManager의 singleton을 가져와 AmongUsRoomManager로 캐스팅
  2. roomData에 저장되어 있는 imposterCount와 maxPlayerCount를 이용해 AmongUsRoomManager 클래스에 각 값들 세팅
  3. 추가로 AmongUsRoomManager 클래스에는 MaxPlayerCount 변수가 필요할 것 같지만 이미 최대 접속자 수를 제한하는데 사용되는 maxConnections라는 멤버 변수가 있습니다. 따라서 roomData의 maxPlayerCount는 이 변수에 저장합니다.
public void CreateRoom(){
        var manager = NetworkManager.singleton as AmongUsRoomManager; // scene에 있는 네트워크 매니저를 가져와서 StartHost를 호출하도록 함
        // 만들고자 하는 방 설정 작업 처리 
        manager.minPlayerCount = roomData.imposterCount == 1 ? 4 : roomData.imposterCount == 2 ? 7 : 9;
        manager.imposterCount = roomData.imposterCount;
        manager.maxConnections = roomData.maxPlayerCount;
        manager.StartHost(); // 서버를 여는 동시에 클라이언트로 게임에 참가하도록 만들어주는 함수

    }

게임을 실행하면 최대 인원 수에 맞춰서 방에 접속할 수 있는 것을 확인할 수 있습니다. (최대 인원 4명인 방에 접속 안됨)

2. 게임 규칙 UI 배치

이전에 Customize UI를 만들면서 Game Option 서브 패널이 있습니다. Color Select 서브 패널을 비활성화시키고 Game Option 서브 패널을 활성화시킨 다음 그 아래에 스크롤 뷰를 추가합니다. (영상의 경우 스크롤뷰를 자식으로 넣었는데 자식으로 넣으니 transform이 모두 비활성화 되어 자식으로 두지 않았습니다.) 그리고 스크롤뷰의 색상 알파값을 0으로 만들어 투명하게 만듭니다.

그 다음 Horizontal을 체크 해제하여 위/아래로만 스크롤이 가능하게 만듭니다.


그리고 실제 게임 옵션 창과 비슷한 움직임을 보이도록 Movement Type을 Clamp로 변경하고 Inertia의 체크를 해제합니다.

그 다음 수직 스크롤 바가 보이지 않도록 Scrollbar Vertical의 Width를 0으로 설정합니다.


스크롤 뷰 안에 들어오는 각 게임 규칙 옵션들이 수직으로 정렬되도록 만들기 위해 Content 오브젝트(Viewport > Content)에 Vertical Layout Group 컴포넌트와 Content Size Fitter 컴포넌트를 붙여줍니다. 세부 사항은 다음과 같이 설정합니다.

다음으로는 각 옵션 항목을 만들겠습니다. Content 아래에 빈 게임 오브젝트를 만들고 그 아래 이미지를 만듭니다. 그리고 이미지 소스에 모서리가 둥근 사각형 sprite를 넣은 후 색을 반투명한 검은색으로 설정합니다. 이미지 앵커는 stretch-stretch로 변경하고 Left, Top, Right, Bottom을 0으로 설정하여 상위 오브젝트의 크기와 일치시켜준 뒤 상위 오브젝트의 크기를 조절해 적당한 크기로 만듭니다. (width: 580, height: 70)

추가로 Inactive Object라는 이름으로 빈 게임 오브젝트를 하나 더 만들어 방금 만든 이미지를 그 아래로 넣어줍니다. 제일 먼저 추천 설정 옵션을 만들겠습니다. Toggle 오브젝트를 하나 추가하고 위치와 크기를 적당히 맞춘 후 Label을 "추천 설정"으로 변경합니다.

  1. Inactive Object 오브젝트와 Toggle 오브젝트 stretch-stretch 설정
  2. Background 크기 조정 및 위치 조정
  3. Label Text 변경 및 Alignment 중간으로 변경

그 다음 GameRuleItem 스크립트를 생성하여 다음과 같이 작성합니다. 이 스크립트는 게임 규칙 설정 UI를 연 플레이어가 호스트가 아닌 클라이언트인 경우 게임 규칙을 수정하지 못하도록 수정 가능한 UI들을 비활성화 시킵니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameRuleItem : MonoBehaviour
{
    [SerializeField]
    private GameObject inactiveObject;
    // Start is called before the first frame update
    void Start()
    {
        if(!AmongUsRoomPlayer.MyRoomPlayer.isServer){
            inactiveObject.SetActive(false);
        }
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

스크립트를 작성한 후 에디터로 돌아와 Recommend Rule 오브젝트의 컴포넌트로 붙이고 프로퍼티에 inactive object를 할당합니다.

그 다음 첫 번째 옵션 오브젝트(Recommend Rule)를 복사하여 Confirm Ejects 항목을 만듭니다. 추가로 긴급 회의 오브젝트도 만듭니다. 긴급회의 오브젝트는 Toggle 버튼을 삭제하고 옵션 셜명을 위한 텍스트, -와 + 버튼 그리고 옵션의 수치 텍스트를 추가합니다.

(왼쪽에 글자 위치를 맞추기 위해 stretch-stretch로 변경 후 Left 값만 20을 주고 나머지는 0을 줍니다. 그리고 Paragraph alignment를 왼쪽, 중앙으로 설정합니다.)

수치 조절형 버튼들을 Inactive Object 아래에 배치하면 클라이언트에서는 +/- 버튼들만 비활성화 되어 게임 룰을 수정은 못하고 볼 수 만 있게 됩니다.

나머지 항목들도 마저 완성해줍니다. (긴급 회의 쿨타임, 회의 제한 시간, 투표 제한 시간)

아래에 추가하는 옵션이 스크롤 영역을 벗어났을 때는 잠시 스크롤 렉트의 뷰포트에 붙어있는 마스크 컴포넌트를 꺼주어 영역 밖의 UI를 보면서 편집합니다.

마저 나머지 항목들을 추가해줍니다. (Anonymous Votes, 이동 속도, 크루원 시야, 임포스터 시야, 킬 쿨타임, 킬 범위, Visual Tasks, Task Bar Updates, 공동 임무, 복잡한 임무, 간단한 임무)

모든 항목을 만든 다음 Content 오브젝트의 Vertical Layout Group 컴포넌트에서 Spacing을 조절하여 적당한 간격을 가지도록 만들어 줍니다. 그리고 Viewport 오브젝트에서 잠시 꺼뒀던 Mask 컴포넌트를 다시 켜줍니다.

여기까지 UI 배치를 모두 마쳤습니다. 게임을 실행해보면 스크롤이 정상적으로 게임 규칙들을 보여주는 모습을 확인할 수있습니다.

  • 게임을 실행해보니 버튼이 옵션 아이템 이미지 뒤에 가려진 것으로 보입니다. 따라서, Hierarchy 창에서 Inactive Object가 가장 하단으로 가게 변경합니다. (저의 경우 Recommend Rule과 Confirm Ejects만 처음 설정을 잘못하여 수정하였습니다.)

3. 게임 규칙 UI 기능 구현

이제 배치한 옵션들을 변경하는 기능을 구현할 차례입니다. 방장이 게임 규칙 값들을 변경하면 다른 클라이언트에서도 변경되어 화면 좌측의 UI에 표시되어야 합니다.

게임 규칙들을 저장하면서 다른 클라이언트들에게 알려줄 GameRuleStore 스크립트를 생성합니다.

게임 규칙 변수 수정 시 게임 규칙을 요약한 UI 업데이트를 작성하는 과정에서 각 변수를 이용해 문자열을 길게 이어붙이는 작업을 상수 문자열 더하기 연산으로 처리하면 성능상 이슈가 있어 StirngBuilder를 이용해 문자열을 만드는 기법을 많이 사용합니다.

Stirng은 변경할 수 없는 형식으로 변경 시 새로운 String을 만들게 되기에 자주 변경하게 되면 성능 저하가 발생함

각 변수들이 변경되면 클라이언트에서 UI를 변경하도록 만들어야하는데, 게임 규칙 변수들이 변경되었을 때 클라이언트에서 UI를 변경하게 만들기 위해서는 hook 함수를 만들어서 등록해줘야 합니다. (Set~_Hook)

  • bool 타입의 toggle 변수는 클라이언트의 CustomizeUI에서 보이지 않을 것이기 때문에 규칙 개요만 업데이트 해줍니다.
  • Text 타입의 변수는 각 Text UI를 업데이트 한 다음 UpdateGameRuleOverview 함수를 호출하도록 만듭니다.

모든 Hook 함수를 작성한 후 각 UI에 대한 상호작용 콜백 함수를 만들어줍니다. (OnChange~)

  • Toggle UI는 콜백이 들어오면 매개변수 value 값을 이용해서 bool 변수를 변경합니다.
  • +/- 버튼으로 값을 조절하는 규칙들은 매개변수로 받는 bool 값으로 +/-를 판정한 다음 각 값이 변해야 하는 단계와 최솟값과 최댓값에 맞춰 값을 변경하도록 만들어줍니다.

최종적으로 작성된 스크립트는 다음과 같습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // 선언
using Mirror; // 선언
using System.Text;

// 킬 범위와 태스크 바 업데이트 주기에 대한 열거형 선언
public enum EKillRange{
    Short, Normal, Long
}
public enum ETaskBarUpdates{
    Always, Meetings, Never
}

// 구조체 생성 후 설정에 필요한 내용들 변수로 선언
public struct GameRuleData{
    public bool confirmEjects;
    public int emergencyMeetings;
    public int emergencyMeetingsCooldown;
    public int meetingsTime;
    public int voteTime;
    public bool anonymousVotes;
    public float moveSpeed;
    public float crewSight;
    public float imposterSight;
    public float killCooldown;
    public EKillRange killRange;
    public bool visualTasks;
    public ETaskBarUpdates taskBarUpdates;
    public int commonTask;
    public int complexTask;
    public int simpleTask;
}

public class GameRuleStore : NetworkBehaviour
{
    [SyncVar(hook = nameof(SetIsRecommendRule_Hook))] 
    private bool isRecommendRule; // 추천 설정
    [SerializeField]   // 각 변수에 맞는 UI 오브젝트 선언 
    private Toggle isRecommendRuleToggle;  // 체크타입은 toggle
    public void SetIsRecommendRule_Hook(bool _, bool value){
        UpdateGameRuleOverview(); // 규칙 개요 업데이트
    }
    public void OnRecommendToggle(bool value){ 
        // isRecommendRule이 true가 되었을 때
        isRecommendRule = value;
        if(isRecommendRule){
            SetRecommendGameRule(); // 게임 규칙 추천 설정으로 만듦
        }

    }
    [SyncVar(hook = nameof(SetConfirmEjects_Hook))] // 값이 변경될 때 클라이언트들에게 통지되게 만듦 
    private bool confirmEjects;
    [SerializeField]  
    private Toggle confirmEjectsToggle;  
    public void SetConfirmEjects_Hook(bool _, bool value){
        UpdateGameRuleOverview(); 
    }
    public void OnConfirmEjectsToggle(bool value){
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false; // 체크 해제
        confirmEjects = value;
    }
    [SyncVar(hook = nameof(SetEmergencyMeetings_Hook))]
    private int emergencyMeetings;
    [SerializeField]
    private Text emergencyMeetingsText; // +/-로 값을 바꾸는 UI는 Text
    public void SetEmergencyMeetings_Hook(int _, int value){
        emergencyMeetingsText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeEmergencyMeetings(bool isPlus){ // 1단위로 0~9
        emergencyMeetings = Mathf.Clamp(emergencyMeetings +(isPlus ? 1 : -1), 0, 9);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetEmergencyMeetingsCooldown_Hook))]
    private int emergencyMeetingsCooldown;
    [SerializeField]
    private Text emergencyMeetingsCooldownText;
    public void SetEmergencyMeetingsCooldown_Hook(int _, int value){
        emergencyMeetingsCooldownText.text = string.Format("{0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeEmergencyMeetingsCooldown(bool isPlus){ // 5단위로 0~60
        emergencyMeetingsCooldown = Mathf.Clamp(emergencyMeetingsCooldown +(isPlus ? 5 : -5), 0, 60);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetMeetingsTime_Hook))]
    private int meetingsTime;
    [SerializeField]
    private Text meetingsTimeText;
    public void SetMeetingsTime_Hook(int _, int value){
        meetingsTimeText.text = string.Format("{0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeMeetingsTime(bool isPlus){ // 5단위로 0~120
        meetingsTime = Mathf.Clamp(meetingsTime +(isPlus ? 5 : -5), 0, 120);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetVoteTime_Hook))]
    private int voteTime;
    [SerializeField]
    private Text voteTimeText;
    public void SetVoteTime_Hook(int _, int value){
        voteTimeText.text = string.Format("{0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeVoteTime(bool isPlus){ // 5단위로 0~300
        voteTime = Mathf.Clamp(voteTime +(isPlus ? 5 : -5), 0, 300);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetAnonymousVotes_Hook))]
    private bool anonymousVotes;
    [SerializeField]  
    private Toggle anonymousVotesToggle;  
    public void SetAnonymousVotes_Hook(bool _, bool value){
        UpdateGameRuleOverview(); 
    }
    public void OnAnonymousVotesToggle(bool value){
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false; // 체크 해제
        anonymousVotes = value;
    }
    [SyncVar(hook = nameof(SetMoveSpeed_Hook))]
    private float moveSpeed;
    [SerializeField]
    private Text moveSpeedText;
    public void SetMoveSpeed_Hook(float _, float value){
        moveSpeedText.text = string.Format("{0:0.0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeMoveSpeed(bool isPlus){ // 0.25단위로 0.5~3
        moveSpeed = Mathf.Clamp(moveSpeed +(isPlus ? 0.25f : -0.25f), 0.5f, 3f);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetCrewSight_Hook))]
    private float crewSight;
    [SerializeField]
    private Text crewSightText;
    public void SetCrewSight_Hook(float _, float value){
        crewSightText.text = string.Format("{0:0.0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeCrewSight(bool isPlus){ // 0.25단위로 0.25~5
        crewSight = Mathf.Clamp(crewSight +(isPlus ? 0.25f : -0.25f), 0.25f, 5f);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetImposterSight_Hook))]
    private float imposterSight;
    [SerializeField]
    private Text imposterSightText;
    public void SetImposterSight_Hook(float _, float value){
        imposterSightText.text = string.Format("{0:0.0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeImposterSight(bool isPlus){ // 0.25단위로 0.25~5
        imposterSight = Mathf.Clamp(imposterSight +(isPlus ? 0.25f : -0.25f), 0.25f, 5f);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetKillCooldown_Hook))]
    private float killCooldown;
    [SerializeField]
    private Text killCooldownText;
    public void SetKillCooldown_Hook(float _, float value){
        killCooldownText.text = string.Format("{0:0.0}s", value);
        UpdateGameRuleOverview();
    }
    public void OnChangeKillCooldown(bool isPlus){ // 2.5단위로 10~60
        killCooldown = Mathf.Clamp(killCooldown +(isPlus ? 2.5f : -2.5f), 10f, 60f);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetKillRange_Hook))]
    private EKillRange killRange;
    [SerializeField]
    private Text killRangeText;
    public void SetKillRange_Hook(EKillRange _, EKillRange value){
        killRangeText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeKillRange(bool isPlus){ // 열거형과 int 타입을 캐스팅하며 1 단위로 0~2
        killRange = (EKillRange) Mathf.Clamp((int) killRange +(isPlus ? 1 : -1), 0, 2);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetVisualTasks_Hook))]
    private bool visualTasks;
    [SerializeField]  
    private Toggle visualTasksToggle;  
    public void SetVisualTasks_Hook(bool _, bool value){
        UpdateGameRuleOverview(); 
    }
    public void OnVisualTasksToggle(bool value){
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false; // 체크 해제
        visualTasks = value;
    }
    [SyncVar(hook = nameof(SetTaskBarUpdates_Hook))]
    private ETaskBarUpdates taskBarUpdates;
    [SerializeField]
    private Text taskBarUpdatesText;
    public void SetTaskBarUpdates_Hook(ETaskBarUpdates _, ETaskBarUpdates value){
        taskBarUpdatesText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeTaskBarUpdates(bool isPlus){ // 열거형과 int 타입을 캐스팅하며 1 단위로 0~2
        taskBarUpdates = (ETaskBarUpdates) Mathf.Clamp((int) taskBarUpdates +(isPlus ? 1 : -1), 0, 2);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetCommonTask_Hook))]
    private int commonTask;
    [SerializeField]
    private Text commonTaskText;
    public void SetCommonTask_Hook(int _, int value){
        commonTaskText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeCommonTask(bool isPlus){ // 1단위로 0~2
        commonTask = Mathf.Clamp(commonTask +(isPlus ? 1 : -1), 0, 2);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetComplexTask_Hook))]
    private int complexTask;
    [SerializeField]
    private Text complexTaskText;
    public void SetComplexTask_Hook(int _, int value){
        complexTaskText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeComplexTask(bool isPlus){ // 1단위로 0~3
        complexTask = Mathf.Clamp(complexTask +(isPlus ? 1 : -1), 0, 3);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }
    [SyncVar(hook = nameof(SetSimpleTask_Hook))]
    private int simpleTask;
    [SerializeField]
    private Text simpleTaskText;
    public void SetSimpleTask_Hook(int _, int value){
        simpleTaskText.text = value.ToString();
        UpdateGameRuleOverview();
    }
    public void OnChangeSimpleTask(bool isPlus){ // 1단위로 0~5
        simpleTask = Mathf.Clamp(simpleTask +(isPlus ? 1 : -1), 0, 5);
        isRecommendRule = false;
        isRecommendRuleToggle.isOn = false;
    }

    // 게임 규칙 요약해서 보여주는 UI에 대한 변수
    [SerializeField]
    private Text gameRuleOverview;
    // 게임 규칙 변수 수정 시 게임 규칙 요약한 UI 업데이트
    public void UpdateGameRuleOverview(){
        var manager = NetworkManager.singleton as AmongUsRoomManager;
        // 게임 규칙을 기반으로 문자열을 만들어 gameRuleOverviewText에 넣어주는 작업
        StringBuilder sb = new StringBuilder(isRecommendRule ? "추천 설정\n" : "커스텀 설정\n");
        sb.Append("맵: The Skeld\n");
        sb.Append($"#임포스터: {manager.imposterCount}\n");
        sb.Append(string.Format("Confirm Ejects: {0}\n", confirmEjects ? "켜짐" : "꺼짐"));
        sb.Append($"#긴급 회의: {emergencyMeetings}\n");
        sb.Append(string.Format("Anonymous Votes: {0}\n", anonymousVotes ? "켜짐" : "꺼짐"));
        sb.Append($"긴급 회의 쿨타임: {emergencyMeetingsCooldown}\n");
        sb.Append($"회의 제한 시간: {meetingsTime}\n");
        sb.Append($"투표 제한 시간: {voteTime}\n");
        sb.Append($"이동 속도: {moveSpeed}\n");
        sb.Append($"크루원 시야: {crewSight}\n");
        sb.Append($"임포스터 시야: {imposterSight}\n");
        sb.Append($"킬 쿨타임: {killCooldown}\n");
        sb.Append($"킬 범위: {killRange}\n");
        sb.Append($"Task Bar Updates: {taskBarUpdates}\n");
        sb.Append(string.Format("Visual Tasks: {0}\n", visualTasks ? "켜짐" : "꺼짐"));
        sb.Append($"공통 임무: {commonTask}\n");
        sb.Append($"복잡한 임무: {complexTask}\n");
        sb.Append($"간단한 임무: {simpleTask}\n");
        gameRuleOverview.text = sb.ToString();       
    }
    // 모든 세팅을 추천 설정으로 맞추는 함수
    private void SetRecommendGameRule(){
        isRecommendRule = true;
        confirmEjects = true;
        emergencyMeetings = 1;
        emergencyMeetingsCooldown = 15;
        meetingsTime = 15;
        voteTime = 120;
        moveSpeed = 1f;
        crewSight = 1f;
        imposterSight = 1.5f;
        killCooldown = 45f;
        killRange = EKillRange.Normal;
        visualTasks = true;
        commonTask = 1;
        complexTask = 1;
        simpleTask = 2;
    }
    // Start is called before the first frame update
    void Start()
    {
        if(isServer){
            SetRecommendGameRule();
        }
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

에디터로 돌아와 새 게임 오브젝트를 생성한 다음 완성한 스크립트를 컴포넌트로 붙여주고, 각 프로퍼티에 해당되는 오브젝트들을 할당합니다. (이때 Game Rule Overview 프로퍼티의 경우 Canvas의 Text를 할당합니다.)

그리고 먼저 Toggle의 On Value Changed 이벤트에 각 이벤트들을 등록합니다.

그 다음 각 +/- 버튼에 맞는 On Click 이벤트를 등록하고 + 버튼의 bool 매개변수에는 체크를 해줍니다.

5. 서브 패널 교체 기능 구현

규칙 세팅 버튼 콜백 등록이 끝나면 위의 색깔 버튼과 게임 버튼에 따라 서브 패널을 교체하는 작업을 진행해야합니다. 먼저 Customize UI 아래의 Color 버튼과 Game 버튼의 Transition 타입을 None으로 변경합니다.

그 다음 Customize UI 스크립트를 열고 두 버튼과 패널을 관리할 변수를 선언합니다. 그리고 ActiveColorPanel, ActiveGameRulePanel 함수를 작성하여 클릭된 버튼에 따라서 활성화된 버튼의 색상을 바꾸고 패널을 전환시키는 코드를 작성합니다. 함수 작성이 끝나면 Customize UI를 활성화했을 때, 자동으로 Color 선택 패널이 활성화 되도록 OnEnable 함수에서 ActiveColorPanel 함수를 호출합니다.

    [SerializeField]
    private Button colorButton;
    [SerializeField]
    private GameObject colorPanel;
    [SerializeField]
    private Button gameRuleButton;
    [SerializeField]
    private GameObject gameRulePanel;
    
    public void ActiveColorPanel(){
        colorButton.image.color = new Color(0f,0f,0f,0.75f);
        gameRuleButton.image.color = new Color(0f,0f,0f,0.25f);

        colorPanel.SetActive(true);
        gameRulePanel.SetActive(false);
    }
    public void ActiveGameRulePanel(){
        colorButton.image.color = new Color(0f,0f,0f,0.25f);
        gameRuleButton.image.color = new Color(0f,0f,0f,0.75f);

        colorPanel.SetActive(false);
        gameRulePanel.SetActive(true);

    }
    public void OnEnable(){
        UpdateColorButton();
        ActiveColorPanel();
        .
        .
    }

코드 작성을 마치고 에디터로 돌아와 버튼과 패널을 컴포넌트의 프로퍼티에 할당해줍니다.

그 다음 각 버튼의 OnClick 이벤트에 콜백 함수를 등록합니다.

그리고 두 패널을 모두 비활성화시키고 CustomizeUI도 비활성화시킵니다. 그리고 게임을 빌드한 후 규칙을 수정해보면 수정한 규칙이 각 클라이언트에 정상적으로 공유되는 모습을 볼 수 있습니다.

profile
하나씩 심어 나가는 개발 농장🥕 (블로그 이전중)

0개의 댓글