추가로 여러 디테일을 추가하거나 버그를 수정할 것이다.
마우스 아래 버튼 크기 키우기
생성 좌표 제한
게임 플레이 경고 이벤트
튜토리얼 ESC 상태설정
생성 선택창 버그 수정
선택 면 버그 수정
ESC BGM Volume버그
마우스 아래 버튼 크기 키우기
마우스 포인터를 버튼 위에 올려두었을 때, 해당 버튼의 텍스트 크기를 키우는 기능을 만들 것이다.
Event Trigger와 버튼 스크립트로 구현하였다.
ButtonUI
using UnityEngine;
using UnityEngine.UI;
public class ButtonUI : MonoBehaviour
{
private int mySize;
[SerializeField] Text myText;
private void Awake()
{
mySize = myText.fontSize;
}
private void OnEnable()
{
myText.fontSize = mySize;
}
public void MouseEnterText() // 마우스가 버튼위로 올라갔을 때
{
myText.fontSize = mySize - 10;
}
public void MouseDownText() // 마우스가 버튼을 클릭 했을 때
{
myText.fontSize = mySize - 20;
}
public void MouseExitText() // 마우스가 버튼을 벗어났을 때
{
myText.fontSize = mySize;
}
}
처음 상태의 자신의 텍스트 크기를 가져오고 마우스를 올렸을 때, 내렸을 때, 클릭했을 때로 함수를 만들어 사용한다.

그리고 EventTrigger를 사용해서 조건에 맞는 함수를 호출한다.
이때, 버튼에 마우스를 올렸을 때 효과음이 나도록 추가해주었다.
이 행위를 사용하는 모든 버튼에 추가해주면 된다. (버튼 프리팹을 습관화하자)
완성본

생성 좌표 제한
플레이어와 적 모두 특정한 위치 내에서만 블럭을 생성할 수 있도록 할 것이다.
Cube Creator
public void InsCube(Vector3 dir, Transform cube, Caster caster) // 큐브가 맞은 방향과 위치를 계산하여 큐브를 생성한다.
{
if (CheckFace(dir, cube)) return;
if (CheckWorld(dir, cube)) return;
if (!CheckCost(cubeCost,caster)) return;
~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~
}
public void Conversion(Vector3 dir, Transform cube, Caster caster) // 큐브 우클릭 시 큐브에 기능추가 함수
{
if (CheckFace(dir, cube)) return;
if (CheckWorld(dir, cube)) return;
if (!CheckCost(resourcePCost,caster)) return;
~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~
}
public void CreateCannon(Vector3 dir, Transform cube, Caster caster)
{
if (CheckFace(dir, cube)) return;
if (CheckWorld(dir, cube)) return;
if (!CheckCost(cannonCost,caster)) return;
~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~
}
bool CheckWorld(Vector3 dir, Transform cube) // 생성할 공간이 지정된 월드 내에 존재하는지 확인
{
Vector3 InsPos = cube.GetComponent<Block>().worldPosition + dir;
if(Mathf.Abs(InsPos.x) > 50 || Mathf.Abs(InsPos.y) > 50 || Mathf.Abs(InsPos.z) > 50)
{
Debug.Log("설치 공간이 영역을 벗어남");
return true;
}
else
{
return false;
}
}
CheckWorld라는 함수를 만들어 현재 생성하는 위치가 생성 영역을 벗어나지 않았는지 판단한다.
생성위치를 절댓값으로 x,y,z를 검사한다.

이렇게 50이상의 좌표 이상으로는 블럭을 생성할 수 없다.
게임 플레이 경고 이벤트
아까 만들었던 생성 위치 제한과 같은 상태에서 게임화면에 텍스트로 사유를 적을 것이다. ~~~한 이유로 생성할 수 없다. 이런식

UIManager
public void WarningUI(string sentence) // 경고문을 출력하는 함수
{
GameObject prefab = Instantiate(warningText, warningTextPivot);
Text prefabText = prefab.GetComponent<Text>();
RectTransform prefabRect = prefab.GetComponent<RectTransform>();
prefabText.text = sentence;
StartCoroutine(WarningFade(prefabRect, prefabText, 2));
}
IEnumerator WarningFade(RectTransform prefabRect, Text text, float duration) // 경고문 Fade와 이동
{
Color color = text.color;
float start = text.color.a;
float end = 0;
Vector2 startPos = prefabRect.anchoredPosition;
Vector2 endPos = new Vector2(prefabRect.anchoredPosition.x, prefabRect.anchoredPosition.y + 100);
float t = 0;
while(t < duration)
{
// Fade
t += Time.unscaledDeltaTime;
color.a = Mathf.Lerp(start, end, t / duration);
text.color = color;
// Move
prefabRect.anchoredPosition = Vector2.Lerp(startPos, endPos, t / duration);
yield return null;
}
Destroy(prefabRect.gameObject);
}
먼저 WarningUI 함수로 출력 문자열을 받고, 새 텍스트를 생성하여 그 텍스트에 문자열을 넣는 식으로 경고문을 화면에 띄운다.
WarningFade 함수를 통해서 경고문의 Fade와 움직임을 관리한다.
텍스트의 Color 알파값과 RectTrasnform의 anchoredPosition을 Lerp로 사용하여 자연스로운 경고문을 출력하게 된다.
InputController
public void InsCube(Vector3 dir, Transform cube, Caster caster) // 큐브가 맞은 방향과 위치를 계산하여 큐브를 생성한다.
{
if (CheckFace(dir, cube,caster)) return;
if (CheckWorld(dir, cube, caster)) return;
if (!CheckCost(cubeCost,caster)) return;
~~~~~~~~~~~~~~~~중략~~~~~~~~~~
}
public void Conversion(Vector3 dir, Transform cube, Caster caster) // 큐브 우클릭 시 큐브에 기능추가 함수
{
if (CheckFace(dir, cube, caster)) return;
if (CheckWorld(dir, cube, caster)) return;
if (!CheckCost(resourcePCost,caster)) return;
~~~~~~~~~~~~~~~중략~~~~~~~~~~~~~~
}
public void CreateCannon(Vector3 dir, Transform cube, Caster caster)
{
if (CheckFace(dir, cube, caster)) return;
if (CheckWorld(dir, cube, caster)) return;
if (!CheckCost(cannonCost,caster)) return;
~~~~~~~~~~~~~~~~~중략~~~~~~~~~~~~~~~~
}
bool CheckFace(Vector3 dir, Transform cube ,Caster caster) // 생성할 공간에 다른 오브젝트가 존재하는지 확인
{
// 배치할 공간에 오브젝트가 있는 확인
bool haveFace = posList.Contains(cube.GetComponent<Block>().worldPosition + dir);
switch(haveFace)
{
case false: Debug.Log("설치가능"); break;
case true: Debug.Log("설치 불가능");
if (caster is InputController)
{
UIManager.Instance.WarningUI("Can't build here.");
}
break;
}
return haveFace;
}
bool CheckWorld(Vector3 dir, Transform cube, Caster caster) // 생성할 공간이 지정된 월드 내에 존재하는지 확인
{
Vector3 InsPos = cube.GetComponent<Block>().worldPosition + dir;
if(Mathf.Abs(InsPos.x) > 50 || Mathf.Abs(InsPos.y) > 50 || Mathf.Abs(InsPos.z) > 50)
{
Debug.Log("설치 공간이 영역을 벗어남");
if(caster is InputController)
{
UIManager.Instance.WarningUI("Outside build area.");
}
return true;
}
else
{
return false;
}
}
bool CheckCost(int cost, Caster caster) // 설치할 구조물의 비용이 충당한지 판단하는 함수
{
if(caster.totalResource < cost) // 비용이 더 큰 경우
{
Debug.Log("자원부족");
if(caster is InputController)
{
UIManager.Instance.WarningUI("Not enough resources.");
}
return false;
}
else
{
Debug.Log("비용지불");
caster.totalResource -= cost;
return true;
}
}
각 생성 코드에 있는 설치 가능 체크 부분 함수를 사용해서 경고문함수를 호출하면 된다.
이때, CheckFace와 CheckWorld의 함수의 경우 caster를 매개변수로 추가하고 이 생성 조건 함수 3개에 플레이어의 생성 호출일 경우에만 경고문을 출력하도록 한다.
완성본

튜토리얼 ESC 설정
현재 튜토리얼 상태에서는 ESC를 클릭할 수 없는 버그가 있는데 이를 수정하고자 한다.
InputController
먼저 키 입력 부분을 수정해준다.
void Interrupt() // 일시정지 코드
{
if(Input.GetKeyDown(KeyCode.Escape))
{
if(GameManager.Instance.myState == GameManager.GameState.TUTORIAL)
{
GameManager.Instance.GameStop(true);
UIManager.Instance.InterruptUI(true);
Time.timeScale = 0;
Debug.Log("튜토리얼 상태에서 일시정지");
}
else if(GameManager.Instance.myState == GameManager.GameState.START)
{
GameManager.Instance.GameStop(true);
UIManager.Instance.InterruptUI(true);
Time.timeScale = 0;
Debug.Log("일시정지");
}
else if (GameManager.Instance.myState == GameManager.GameState.STOP)
{
GameManager.Instance.GameStop(false);
UIManager.Instance.InterruptUI(false);
Time.timeScale = 1;
Debug.Log("일시정지 해제");
}
}
}
앞에 if문을 추가하여 현재 튜토리얼 상태일 때의 코드를 추가해준다.
Start와 함께 조건문을 작성해도 되지만, 혹시 모를 상황에 대비하여 나눠주었다.
GameManager
public void GameStop(bool myBool) // 게임을 정지하는 단계
{
if(myBool)
{
myState = GameState.STOP;
AudioManager.Instance.BGMFinish();
}
else
{
if(TutorialManager.Instance.isTutorial)
{
myState = GameState.TUTORIAL;
}
else
{
myState = GameState.START;
}
AudioManager.Instance.StartBGM();
}
}
이후 실질적으로 게임을 정지시키고 정지해제시키는 코드에서, 정지 해제부분에서 튜토리얼 상태라면 다시 튜토리얼 상태로 돌려보내는 조건문을 추가해준다.
TutorialManager
게임이 종료되고, 메인 씬으로 다시 향할때, 현재 튜토리얼 단계의 흔적들을 모두 지우는 작업을 해야한다.
private void OnEnable()
{
GameManager.Instance.mainScene += MainReset;
}
private void OnDisable()
{
GameManager.Instance.mainScene -= MainReset;
}
public void SeletePanel(bool state) // 생성 튜토리얼 블럭 생성창 // 튜토리얼 단계
{
if(resourceCoroutine != null)
{
StopCoroutine(resourceCoroutine);
}
if(myState == TutorialState.RESOURCE)
{
seletePanelText.text = "Click or W";
seletePanel.GetComponent<RectTransform>().anchoredPosition = new Vector3(0, 0, 0);
}
~~~~~~~~~~~~ 중략~~~~~~~~~~~~
}
void MainReset() // 메인으로 향했을 때 튜토리얼 리셋
{
if(isTutorial)
{
StopAllCoroutines();
isTutorial = true;
totalCameraScroll = 0;
totalCameraMove = 0;
cameraGroup.alpha = 0;
resourceGroup.alpha = 0;
seletePanel.alpha = 0;
blockGroup.alpha = 0;
cannonGroup.alpha = 0;
Destroy(currentSeletePlane);
dialogText.text = "";
myState = TutorialState.CAMERAMOVE;
tutorialText.SetActive(false);
}
}
먼저 메인으로 향할때 호출되는 델리게이트를 구독하는 MainReset 함수를 만든다.
이 함수에서는 튜토리얼을 초기화 하는 코드를 작성한다. 모든 데이터를 초기화 하며, StopAllCoroutines를 사용하여 진행중인 모든 코루틴또한 정지시킨다.
이때 if문으로 현재 튜토리얼 상태인지를 판단하여, 이미 튜토리얼을 완료했으면 다시 튜토리얼을 하는 일이 없게한다.
SeletePanel함수에서는 다시 실행되었을 때, 기본값으로 들어가는 코드를 추가해주었다.
완성본

생성 선택창 버그 수정
생성 선택창이 나오고 나서 Exit로 게임을 나가게 되면 생성 선택이 그대로 남아있는 버그를 수정할 것이다.

SeleteBlock
private void OnEnable()
{
GameManager.Instance.gameInit += GameReset;
}
private void OnDisable()
{
GameManager.Instance.gameInit -= GameReset;
}
private void GameReset() // 게임이 리셋될 때, 위치 초기화
{
StopAllCoroutines();
seletePanel.anchoredPosition = firstPos;
}
게임이 초기화 될때 호출되는 델리게이트에 위치를 초기화 시키는 GameReset 함수를 구독시킨다.
선택 면 버그 수정
선택창과 동일하게 선택중일 때, 게임을 나가면 계속 강조되는 위치가 남아있는 버그를 해결한다.

SeletePlane
private void Start()
{
GameManager.Instance.gameInit += GameReset;
}
private void OnDisable()
{
GameManager.Instance.gameInit -= GameReset;
}
void GameReset()
{
StopAllCoroutines();
Destroy(activeObject);
}
SeleteBlock과 동일하게 코드를 작성해 준다. OnEnable에서 구독을 할 경우 오류가 나는 경우가 있어서 Start에서 구독해주었다.
ESC BGM Volume 버그
BGM이 Fade중일 때 다시 Fade시 값이 이상하게 적용되는 것과, ESC 일시정지 상태에서 BGM 볼륨 변화가 되지않는 버그를 수정하였다.
GameManager
public void GameExit() // 일시정지 상태에서 Exit 버튼을 눌렀을 때,
{
AudioManager.Instance.StartBGM();
UIManager.Instance.InterruptUI(false);
MainScene();
}
먼저 Exit 버튼을 눌렀을 때, 다시 소리가 커지는 코드를 추가해주었다.
AudioManager
private float finalVolume = 1;
public void SetBGMVolume(float value)
{
backGroundAudio.volume = value * optionBGMWeight;
finalVolume = value;
}
IEnumerator BGMFade(bool type) // type = 1 : FadeIn / type = 0 : FadeOut
{
float t = 0;
float duration,end;
float start = backGroundAudio.volume;
switch(type)
{
case true: duration = 5; end = finalVolume; break;
case false: duration = 0.5f; end = finalVolume * optionBGMWeight; break;
}
while(t < duration)
{
// t += Time.deltaTime;
t += Time.unscaledDeltaTime;
backGroundAudio.volume = Mathf.Lerp(start, end, t / duration);
yield return null;
}
}
public void StartBGM() // 게임이 시작될 때, 실행할 오디오를 구하는 함수
{
int level = GameManager.Instance.enemyLevel;
int stage = GameManager.Instance.enemyStage;
int id = level * 1000 + stage;
Debug.Log("id는 : " + id);
if(id == 1000)
{
BGMData("BGM/Sunset on the Bay (Electronic, Synthwave)");
}
else if(id == 1001 || id == 1002)
{
BGMData("BGM/Battle Song");
}
~~~~~~~~~~~~~~~~~~~중략~~~~~~~~~~~~~~
}
이후 StartBGM에서 튜토리얼 상태의 소리를 넣어주었다.
finalVolume이라는 변수로 Fade중에 소리를 변화시켜도 최종 목적지를 지정해주어서, 사용하기 떄문에 문제가 되지 않는다.
값을 설정할 때, finalVolume에 넣어주고, Fade를 할 때, 소리가 켜질때는 finalVolume을, 작아질 때는 가중치를 곱해서 사용한다.
'GameDev > Cubidom' 카테고리의 다른 글
| Cubidom - 제약 시스템 (0) | 2026.05.07 |
|---|---|
| Cubidom - AI 시스템 보완 (Utility AI) (0) | 2026.05.03 |
| Cubidom - 디테일 추가 및 버그 수정 1 (0) | 2026.04.29 |
| Cubidom - FTUE 3 (0) | 2026.04.28 |
| Cubidom - FTUE 만들기 2 (0) | 2026.04.27 |