이번엔 초반 튜토리얼, 플레이어가 게임의 조작법을 알려주는 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();
}
}
}
게임이 시작되었을 때, 카메라무브까지 플레이어가 행동할 수 있어야 하기 때문에 카메라무브의 코드를 수정했다. (타겟 변경은 되지 않는다.)

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

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에 더해주었다.

시스템 흐름

'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 |