카메라 버그수정
현재 게임이 시작되고, 카메라를 회전시키지 않은채 카메라의 타겟을 정하게 되면 카메라의 위치가 이상해지는 버그가 존재한다.

이 버그는 CameraMovement에서 offset의 값이 정해지지 않은채, 타겟을 잡을 때 offset을 사용하기 때문에, 발생하는 버그로 발견되었다.
CameraMovement
void gameStartCam()
{
ChangeCamera(playingCam);
// pitch = transform.rotation.eulerAngles.x;
// yaw = transform.rotation.eulerAngles.y;
Vector3 currentRotation = controllCam.transform.rotation.eulerAngles;
pitch = currentRotation.x;
yaw = currentRotation.y;
Quaternion rotation = Quaternion.Euler(pitch, yaw, 0);
offset = rotation * new Vector3(0, 0, -distance);
}
게임이 시작될 때, offset의 값을 현재 컨트롤중인 카메라의 회전과 위치를 사용해 바르게 맞춰주면 된다.
주석된 부분은 과거 사용했던 코드
완성본

맵 배경 변경
현재까지 맵(바닥)과 Camera의 BackGround를 동일한 색(흰색)으로 하여 Orthographic 카메라의 위화감이 없도록 했다.

카메라의 시야가 바닥을 뚫어도, 같은 흰색이기 때문에 상관이 없었다. 그런데 이 교차선을 보면 환경 빛 때문에 아주 약간의 노란색 선이 생기는데, 이를 방지 하기 위해 두가지 방법이 존재한다.

1. 바닥 material색 바꾸기

사용하는 바닥색의 Material을 Unlit으로 하여, 빛에 상관없게끔 하면 노란색 줄을 지울수 있다.
다만 바닥에 생성되는 오브젝트의 그림자는 생성되지 않는다.
2. 빛의 색 바꾸기
사용하는 방식은 이 방식으로 빛의 색을 하얀색으로 바꾸면 노란색 줄이 사라지게 된다.

원래 이렇게 사용하려고 했는데, Direction Light 특성상 플레이 그라운드의 모든 6면이 같은 색을 가지기 어렵기 때문에 1번 방법과 2번 방법을 섞어서 사용하기로 했다.

바닥의 Material은 그림자를 위해 2번 방법으로 사용하되, 아랫면을 제외한 5면의 Material을 Unlit으로 하여 모든 배경이 하얀색이 되게끔 해주었다.

바닥을 제외한 면의 Material을 Unlit 하얀색으로 바꿔준다.
완성본

선택 UI 수정하기
현재까지 큐브를 클릭하고 생성 블럭을 선택하는 UI가 선택한 화면 위치에 생성되었는데, 생성위치와 UI를 수정해줄 것이다.
또한 선택창에서는 타겟을 전환할수 있게끔 해줄 것이다.
먼저 UI쪽을 생각해 보자면 선택창은 아랫쪽에서 나오며, 단축키로도 생성할 수 있게끔 할 것이다.

한 패널에 수평 레이아웃 그룹으로 정렬하고, 각각 버튼과 이미지, 네임 텍스트와 단축키 텍스트로 만들었다.
각 버튼은 기존 선택창 버튼과 동일하게 만들었다.
SeleteBlock
using System.Collections;
using UnityEngine;
public class SeleteBlock : MonoBehaviour
{
[SerializeField] RectTransform seletePanel; // 선택창
Vector2 endPos = new Vector2(0,0); // 올라올때 최종위치
Vector2 firstPos = new Vector2(0, -150); // 내려갈때 최종위치
public bool isFloating;
Coroutine myCoroutine;
public void Execute() // 선택창 실행
{
if(myCoroutine != null && !isFloating) // 현재 내려가는 중일 때
{
StopCoroutine(myCoroutine); // 내려가기 중지
}
myCoroutine = StartCoroutine(MovingPanel(true)); // 올라가기 시작
isFloating = true; // 올라가는중
}
public void Finish() // 선택창 해제
{
if(myCoroutine != null && isFloating) // 현재 올라가는 중일 때
{
StopCoroutine(myCoroutine); // 올라가기 중지
}
if(isFloating) // 올라가는 중일때 단 한번만 내려가기 입력 받기
{
myCoroutine = StartCoroutine(MovingPanel(false));
isFloating = false;
Debug.Log("내려가기");
}
}
IEnumerator MovingPanel(bool type) // UI 떠오르게 하기 true : 올라감 , false : 내려감
{
float t = 0;
float duration = 0.5f;
Vector2 startPos = seletePanel.anchoredPosition;
Vector2 LastPos = type ? endPos : firstPos;
while(t <= duration)
{
float per = t / duration;
seletePanel.anchoredPosition = Vector2.Lerp(startPos, LastPos, per);
t += Time.deltaTime;
yield return null;
}
seletePanel.anchoredPosition = LastPos;
Debug.Log("움직이기 끝");
}
}
이 스크립트에서는 기존 선택 UI창처럼 생성과, 파괴로 되지 않고, Lerp를 사용하여 활성화는 창이 올라오고 비활성화는 창이 내려가는 식으로 구현된다.
Execute함수는 선택창이 활성화 되는 함수로, UI창이 올라가게한다. isFloating을 사용하여, 내려가는 중일 때는 기존 코루틴 함수를 정지시키고 실행한다.
Finishi함수는 선택창이 비활성화 되는 함수로 UI창이 내려가게 하며 Execute함수와 비슷하게 작동하는데 한가지 다른점은 Finishi함수는 호출이 Update에서도 되기 때문에, 코루틴 함수 실행을 isFloating으로 호출받을 때 한번만 실행된다.
MovingPanel코루틴 함수는 bool형으로 type을 받고, 삼항연산자를 사용하여, type값에 따라서 최종 목표위치를 변경한다.
시간과 Lerp를 사용하여 특정 시간동안 현재 위치에서 목표위치까지 이동한다.
참고로 UI의 위치 이동은 RectTransform.anchoredPosition으로 움직인다.
UIManager
[SerializeField] SeleteBlock seleteBlock;
public void ShowSeleteBlock(Vector3 mousePos)
{
// print("블럭 선택창 보여줘");
// seleteBlock.transform.position = mousePos;
// seleteBlock.SetActive(true);
seleteBlock.Execute();
}
public void InActiveSeleteBlock()
{
// seleteBlock.SetActive(false);
seleteBlock.Finish();
}
void FinishUI()
{
finishPanel.SetActive(true);
// seleteBlock.SetActive(false);
InputController.Instance.CancelSelete();
if (GameManager.Instance.winner == GameManager.Winner.ENEMY)
{
enemyWin_t.SetActive(true);
retryButton.SetActive(true);
}
else if(GameManager.Instance.winner == GameManager.Winner.PLAYER)
{
playerWin_t.SetActive(true);
if (GameManager.Instance.enemyStage < 5)
{
nextButton.SetActive(true);
}
}
}
중간 연결다리 역할을 하는 UIManager의 변경점이다. SetActive로 활성화 시키는 것이 아닌, SeleteBlock 스크립트의 함수를 호출하는 식으로 변경되었다.
InputController
public void seleteBlock(int num)
{
if(isSeleting)
{
isSeleting = false;
UIManager.Instance.InActiveSeleteBlock();
switch (num)
{
case 1:
Debug.Log(hit.normal + " " + hit.transform);
cubeCreator.InsCube(hit.normal, hit.transform, this); // 큐브 생성하기
break;
case 2:
cubeCreator.Conversion(hit.normal, hit.transform, this); // 큐브 생성하기
break;
case 3:
cubeCreator.CreateCannon(hit.normal, hit.transform, this); // 큐브 생성하기
break;
default: break;
}
seletePlane.DeletePlane();
}
}
버튼을 눌렀을 때 호출되는 seleteBlock을 isSeleting으로 감싸서, UI창이 내려갈 때, 여러번 버튼을 눌러도 한번만 동작할 수 있게한다.

단축키 만들기
InputController
void Update()
{
if(GameManager.Instance.myState == GameManager.GameState.START)
{
LeftClick();
OnMouseRay();
ShotKey();
}
Interrupt();
}
void ShotKey() // 단축키 입력
{
if(seletePanel.isFloating) // 단축키 사용가능할 때
{
if(Input.GetKeyDown(KeyCode.Q))
{
seleteBlock(1);
}
else if (Input.GetKeyDown(KeyCode.W))
{
seleteBlock(2);
}
else if (Input.GetKeyDown(KeyCode.E))
{
seleteBlock(3);
}
}
}
Shotkey에서 단축키 사용이 가능할 때, 해당 키들을 누르게 되면 seleteBlock함수를 호출하는 식으로 단축키를 만들어 준다.
선택중 다른 선택 수정
선택중일 때, 다른 큐브의 면을 클릭하게 되면 마지막에 클릭한 면으로 선택과 생성이 되게끔 해준다.
InputController
RaycastHit hit2;
void LeftClick() // 블럭을 좌클릭 하면 생성할 블럭의 종류 UI를 표시한다.
{
if(Input.GetMouseButtonDown(0) && !isSeleting)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 카메라로 부터 클릭한 월드좌표까지 광선
if (Physics.Raycast(ray, out hit, Mathf.Infinity, LayerMask.GetMask("Cube"))) // 광선에 맞은 충돌체를 담는다.
{
if (hit.transform.GetComponent<Block>().myCaster == this)
{
UIManager.Instance.ShowSeleteBlock(Input.mousePosition); // 큐브 선택창 생성
isSeleting = true;
seletePlane.InsPlane(hit);
// cubeCreator.InsCube(hit.normal, hit.transform, this); // 큐브 생성하기
}
camera_M.ChengeTarget();
Debug.Log("큐브 클릭");
cubeShader.CubeOutLine(hit.transform.gameObject);
previewManager.PreviewObject(hit);
}
}
else if(Input.GetMouseButtonDown(0)) // 선택중일 때, 다른 자신의 큐브 클릭 처리
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 카메라로 부터 클릭한 월드좌표까지 광선
if (Physics.Raycast(ray, out hit2 , Mathf.Infinity, LayerMask.GetMask("Cube"))) // 광선에 맞은 충돌체를 담는다.
{
hit = hit2;
if (hit.transform.GetComponent<Block>().myCaster == this) // 자신의 큐브를 선택했을 때 선택 면 재위치
{
seletePlane.DeletePlane();
seletePlane.InsPlane(hit);
}
else // 다른 큐브를 선택했을 때 선택못하게하기
{
CancelSelete();
UIManager.Instance.InActiveSeleteBlock();
}
camera_M.ChengeTarget(); // 타겟 변경
cubeShader.CubeOutLine(hit.transform.gameObject); // 외곽선 처리
previewManager.PreviewObject(hit); // 미리보기 처리
}
}
}
선택하는 중일 때, 다른 큐브를 클릭했을 때의 조건을 추가하여, 행동을 처리한다.
hit2를 통해서 큐브가 아닌 충돌체의 검사를 미리 하고, 큐브일 떄, 직접적인 생성에 관여하는 hit에 넣어준다.
만약 자신의 큐브라면 강조면의 위치를 재위치시키고, 만약 다른 큐브였다면 선택을 못하게 한다.
이후 큐브라면 타겟을 변경하고, 외곽선 처리와 미리보기 처리를 한다.
처음에는 isSeleting 조건을 없애려고 했는데, 그렇게 하니, 선택창에서 클릭을 할때, 다른 충돌체를 hit에 넣어서 제대로 생성이 되지 않기 때문에 이렇게 작성했다.
완성본

'GameDev > Cubidom' 카테고리의 다른 글
| Cubidom - 블렌더 오브젝트 시스템 연결시키기 및 버그수정 (1) | 2026.04.14 |
|---|---|
| Cubidom - 블렌더 큐브 오브젝트 가져오기 (0) | 2026.04.11 |
| Cubidom 시인성 향상3 (0) | 2026.04.04 |
| Cubidom 시인성 향상2 (0) | 2026.04.01 |
| Cubidom 시인성 향상1 (0) | 2026.03.31 |