GameDev/Cubidom

Cubidom - FTUE 만들기 1

SMNNMN 2026. 4. 26. 22:10

이번엔 초반 튜토리얼, 플레이어가 게임의 조작법을 알려주는 FTUE를 만들어 볼 것이다.

처음 게임이 플레이하면 게임이 즉시 시작되고, UI 아이콘들을 통해 화면 회전, 블럭 생성, 튜토리얼 적 처치까지 진행된 이후, 다시 메인화면으로 이동하게끔 만들 것이다. 

 

개발 순서는 튜토리얼 순서대로 따라가면서 만들 것이다. 

 

튜토리얼 시작부터 게임 시작까지

TutorialManager

튜토리얼의 시스템을 총괄하는 스크립트

using UnityEngine;

public class TutorialManager : MonoBehaviour
{
    private static TutorialManager instance;
    public static TutorialManager Instance
    {
        get { return instance; }
    }
    public bool isTutorial { get; private set; } = true; // 튜토리얼 상태인지 판단

    public enum TutorialState // 튜토리얼의 단계 (할수 있는 입력이 늘어난다.)
    {
        CAMERAMOVE = 0, // 카메라 조작 단계
        RESOURCE = 1, // 생산자 생성 단계
        BLOCK = 2, // 블럭 생성 단계
        CANNON = 3, // 대포 생성 단계
    }
    public TutorialState myState;

    [SerializeField] GameObject tutorialText;

    void Awake()
    {
        if(instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
        
        myState = TutorialState.CAMERAMOVE; // 현재 할수 있는 행동은 카메라 제어까지
}

public void TutorialStart() // start 버튼이후 플레이 시작
    {
        tutorialText.SetActive(true);
        GameManager.Instance.GameStart(); // 튜토리얼 시작
    }
}

isTutorial 이라는 변수로 현재 튜토리얼 상황인지 아닌지를 다른 스크립트에서 판단한다. 

TutorialState를 통해 현재 튜토리얼 몇 단계인지를 판단한다. 

CAMERAMOVE는 카메라움직임, RESOURCE는 자원생산자 생성, BLOCK은 블럭 생성 및 타겟 변경, CANNON은 대포 설치이다.

TutorialStart 함수는 플레이어가 Start버튼을 눌렀을 때의 로직을 관리한다.

 

GameManager

    public enum GameState
    {
        STANDBY, // 게임 시작전 대기화면 상태
        TUTORIAL, // 튜토리얼 상태
        START, // 게임 시작하고 진행하는 상태
        STOP, // 게임 일시정지 상태
        FINISH, // 게임이 끝난 상태
    }

	public void EnemySelete() // 적 난이도를 선택하는 단계
    {
        if(TutorialManager.Instance.isTutorial) // 튜토리얼 단계일때
        {
            
            TutorialManager.Instance.TutorialStart();
            return;
        }
        enemySelete?.Invoke();
    }    
    public void GameStart() //  게임이 본격적으로 시작되는 단계
    {
        if(TutorialManager.Instance.isTutorial) // 튜토리얼 중일 때
        {
            myState = GameState.TUTORIAL;
            
            gameStart?.Invoke();
            return;
        }

        myState = GameState.START;
        gameStart?.Invoke();
    }

GameState에서 TUTORIAL이라는 옵션을 추가했다. 

기존 Start 버튼을 눌렀을 때, EnemySelete 함수가 호출되기 때문에, 이 함수에서 튜토리얼 상태일때는 튜토리얼을 시작하고 즉시 함수를 종료한다. 

GameStart 함수를 통해 게임이 시작되었을 때, 튜토리얼이라면 GameState를 TUTORIAL로 바꾸고 게임시작 델리게이트를 호출한 이후 종료한다. 

 

UIManager

    void playStart() // 게임시작 시 UI
    {
        if(GameManager.Instance.myState == GameManager.GameState.TUTORIAL) // 튜토리얼 상태일때
        {
            startButton.SetActive(false);
        }
        enemyLevels.SetActive(false);
        playButton.SetActive(false);

        resourceValue_T.gameObject.SetActive(true);
        levelStage_T.text = "Level : " + GameManager.Instance.enemyLevel_E.ToString() + "\nStage : " + GameManager.Instance.enemyStage.ToString();
        levelStage_T.gameObject.SetActive(true);
    }

playStart 함수의 경우 게임이 시작될 때, 호출되는 함수인데 튜토리얼 상태일 때는 start 버튼을 비활성화 해주는 코드를 추가한다. 

CameraMovement

    void Update()
    {
        if(GameManager.Instance.myState == GameManager.GameState.START && !brain.IsBlending)
        {
            Move();
            Scroll();
            // ChengeTarget();
            TargetCheck();
            
        }
        else if (GameManager.Instance.myState == GameManager.GameState.TUTORIAL && !brain.IsBlending) // 튜토리얼 상태일때
        {
            if (TutorialManager.Instance.myState >= TutorialManager.TutorialState.CAMERAMOVE)
            {
                Move();
                Scroll();
                // ChengeTarget();
                TargetCheck();
            }
        }
    }

게임이 시작되었을 때, 카메라무브까지 플레이어가 행동할 수 있어야 하기 때문에 카메라무브의 코드를 수정했다. (타겟 변경은 되지 않는다.)

Start 버튼을 눌렀을 때, 즉시 게임이 시작되며 카메라무브를 제외한 행동을 할 수 없다.

 

카메라 제어 튜토리얼

카메라 회전과 줌인 튜토리얼을 만들 것이다. 

TutorialManager

     public float totalCameraMove = 0;
    public float totalCameraScroll = 0;
    private float maxCameraMove = 700;
    private float maxCameraScroll = 30;
    [SerializeField] CanvasGroup cameraGroup; // 카메라 제어 UI
    [SerializeField] Image cameraMoveBar; // 카메라 회전 게이지바
    [SerializeField] Image cameraScrollBar; // 카메라 휠 게이지바
 
 	private void Update()
    {
        if(GameManager.Instance.myState == GameManager.GameState.TUTORIAL)
        {
            cameraMoveBar.fillAmount = totalCameraMove / maxCameraMove;
            cameraScrollBar.fillAmount = totalCameraScroll / maxCameraScroll;

            if(totalCameraMove >= maxCameraMove && totalCameraScroll >= maxCameraScroll)
            {
                myState = TutorialState.RESOURCE;
                StartCoroutine(UIFade(cameraGroup, false));
            }
        }
    }
    public void TutorialStart() // start 버튼이후 플레이 시작
    {
        tutorialText.SetActive(true);
        GameManager.Instance.GameStart(); // 튜토리얼 시작
        StartCoroutine(UIFade(cameraGroup,true));
    }
    IEnumerator UIFade(CanvasGroup canvas, bool state)
    {
        canvas.gameObject.SetActive(true);
        float start, end, duration;
        if(state) // FadeIn
        {
            start = 0;
            end = 1;
            duration = 3;
        }
        else // FadeOut
        {
            start = 1;
            end = 0;
            duration = 1;
        }


            float t = 0;
        while(t < duration)
        {
            t += Time.deltaTime;
            canvas.alpha = Mathf.Lerp(start, end, t / duration);
            yield return null;
        }
        canvas.alpha = end;
    }

totalCameraMove는 플레이어가 카메라를 회전시켰을 때, 더해지는 추가값, totalCameraScroll은 카메라를 줌인아웃을 했을때 더해지는 추가값으로, 플레이어가 작동방식을 잘 알고있는지 확인하는데 사용한다. 바로 아래 max값에 도달한다면 잘 이해함을 알수있다. 

Update에서는 현재 튜토리얼 실행 게이지를 보여주고, 모두 완료한다면 다음 튜토리얼로 넘어간다. 

TutorialStart함수에서 튜토리얼이 시작하면 카메라 제어 튜토리얼 UI가 Fade로 생성된다. 

UIFade 코루틴함수를 통해 UI가 Fade In/Out 된다. 

이때 CameraGroup 컴포넌트를 사용했는데, 이 경우 알파값으로 UI의 투명도를 조절하기 쉬워진다. 

 

CameraMovement

    void Move()
    {
        if(Input.GetMouseButton(1) && !isMoving)
        {
            ~~~~~~~~~~~~~~(중략)~~~~~~~

            float yawPlus = Input.GetAxis("Mouse X") * Time.deltaTime * sensitivityX;
            float pitchPlus = Input.GetAxis("Mouse Y") * Time.deltaTime * sensitivityY;

            yaw += yawPlus;
            pitch -= pitchPlus;

            TutorialManager.Instance.totalCameraMove += Mathf.Abs(yawPlus) + Mathf.Abs(pitchPlus); // 튜토리얼 카메라 회전
            
            ~~~~~~~~~~~~~~~~~~~~(중략)~~~~~~~
        }
    }
    void Scroll()
    {
        float zoomPlus = Input.GetAxis("Mouse ScrollWheel") * zoomSensitivity;
        zoom -= zoomPlus;

        TutorialManager.Instance.totalCameraScroll += Mathf.Abs(zoomPlus); // 튜토리얼 카메라 줌인아웃

        ~~~~~~~~~~~(중략)~~~~~~~~~~
    }

 

CameraMovement에서 카메라 회전값과 스크롤값을 가져오는데, 새롭게 yawPlus와 pitchPlus를 만들어 회전의 절댓값을 가져오는데 수월히 작성했고, Scroll의 경우에도 zoomPlus라는 변수를 만들어 totalCameraScroll에 더해주었다. 

카메라를 줌인할 때, Camera Zoom 게이지가 차오르고 카메라를 회전할 때, Camera Rotate게이지가 차오른다. 둘 모두 다 차오르면 사라진다.

 

시스템 흐름

'GameDev > Cubidom' 카테고리의 다른 글

Cubidom - FTUE 3  (0) 2026.04.28
Cubidom - FTUE 만들기 2  (0) 2026.04.27
Cubidom - 전체적인 디자인 추가  (0) 2026.04.25
Cubidom - SFX 넣기 2  (0) 2026.04.22
Cubidom - SFX 넣기 1  (0) 2026.04.20