클릭하면 해당 오브젝트 선택. 그 후 삭제 버튼 누르면 삭제.
씬 저장 기능 Inspector 체크박스로 넣어놓기 (easy save 기능 알아보기)
점과 점 사이를 선으로 연결하기
플레이어 박자선 만들기
박자 맞춰서 누르면 이펙트
박자 얼마나 정확하게 맞췄는지 따라 종류 구분하여 저장
드롭다운이 아니라 버튼 눌러서 선택해놓기
MapEditor 자녀에 ObjectSelector 만들고
Change나 Delete 버튼 누르면 ObjectSelector에서 선택했던 블럭 가져와서
블럭 종류 바꾸거나 삭제

빈 게임 오브젝트인 MapEditor에 MapEditor 스크립트 붙여놓고
맵 에디팅에 필요한 UI와 ObjectSelector를 자녀로 넣어서 프리팹화해도 레퍼런스 안 깨지게
검색하여 할당하는 과정을 없애고 버튼 이벤트 할당으로 합쳐서 코드도 더 간결하게 만들었다.
void Start()
{
InitCreateButton();
InitChangeButton();
}
void InitCreateButton()
{
createButton = GetComponent<Button>();
createButton.onClick.AddListener(CreateNewBlock);
}
void InitChangeButton()
{
changeButton = GetComponent<Button>();
changeButton.onClick.AddListener(ChangeBlock);
}
리팩토링 이전 (사실 create 버튼에 달아놨었던 거라 작동 안 함 이거)
void Start()
{
InitButtonEvent();
}
void InitButtonEvent()
{
createButton.onClick.AddListener(CreateNewBlock);
changeButton.onClick.AddListener(ChangeBlock);
}
리팩토링 이후
ObjectSelector.cs 만들었는데
void SelectObject()
{
// 카메라에서 마우스 위치로 Ray 발사
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(mousePosition, Vector2.zero);
// 선택한 오브젝트가 있었다면 일단 선택 취소
if (selectedObject != null)
DeselectObject(selectedObject);
// 블럭 클릭했으면 해당 블럭 선택
if (hit.collider != null && hit.collider.gameObject.CompareTag("Note"))
SelectNewObject(hit.collider.gameObject);
}
작동을 안 함

로그 찍어보니까 아무것도 선택이 안됨

콜라이더를 안 달아준거였음
바보

마우스 클릭하면 선택된다
변경 버튼은 누르면 다른 블럭 위치도 다 바뀌어야 하는거라
점선도 구현해야되고 맵 전체 블럭에 대한 정보를 담고 있어야 하는데
아직 먼 얘기니까 누르면 종류만 바뀌는 식으로 간단 구현

구현하고 테스트해봤는데 버튼 클릭하는 순간 선택이 풀려서 원하는대로 동작이 안됐음
당장 문제를 해결하고자 한다면 다른 곳 선택했을 때 선택 해제하는 기능을 없애고
선택 해제 버튼을 따로 만들면 되긴 함.
근데 굳이 불편하게 그럴 이유가? 좀만 더 해보기.
void DeselectObject(GameObject obj)
{
StartCoroutine(DelayedAction());
selectedObject = null;
Debug.Log("Deselected: " + obj.name);
}
private IEnumerator DelayedAction()
{
yield return null; // 다음 프레임까지 대기
}
한 프레임 기다려봤는데 안됨.
WaitForEndOfFrame 써봐도 안됨.
private IEnumerator DelayedAction()
{
yield return new WaitForSeconds(2.0f);
Debug.Log("다 기다림");
}
이렇게 했더니 선택 취소 바로 해버린 다음 2초 기다렸다가 로그 뜸.
gpt한테 물어봤더니 그따구로 코드 짜면 당연히 비동기니까 안 기다려준다고 함.
생각해보니까 당연하다. 바본가? IEnumerator 안에 넣어야지.

2초 기다렸을 때는 됨.
근데 1프레임 기다렸을 땐 안되길래 또 물어봤더니
응 ^^ 삭제는 다음 프레임에 이루어져 ^^ 라고 함.
IEnumerator DeselectObject(GameObject obj)
{
yield return null;
yield return null;
yield return null;
yield return null;
selectedObject = null;
}
아니 근데 4프레임이나 기다려도 여전히 deselect 하고 나서 삭제하는데? 뭐임 이거

이런 것도 있다고는 함
IEnumerator DeselectObject(GameObject obj)
{
yield return new WaitForSeconds(0.01f);
selectedObject = null;
}
실험을 좀 해봤는데, WaitForSeconds(0.01f)는 deselect가 먼저 되고,
WaitForSeconds(0.1f)부터는 delete가 된 후 deselect가 됨.
더 똑똑한 gpt한테 물어봤는데 이 친구도 원인은 해명 못하고
"ㅋㅋ 그냥 코드 뜯어고치셈" 라고 함.
간단한 해결책으로는 버튼들에 UI 태그 달아놓고 UI 태그면 선택 취소 안 하도록 할 수 있겠고
아니면 지금처럼 WaitForSeconds 해놓으면 되기야 할텐데
일단 WaitForSeconds로 미봉책 해놓고 넘어가자.
문제가 될 만한 부분은 나~중에 유저가 맵 에디터를 사용하게 됐을 때
컴퓨터 성능에 따라서 뭔가 뭔가가 느려져서 삭제가 안 될 가능성이 있는데,
그때마저도 게임 로직에 핵심적인 부분 아니라 살짝 후순위일 문제를
지금 미리 생각하면서 속도에 발목 잡힐 필요 없다.
UI 태그 다는 건 귀찮아서 안 할라그랬는데,
쓰다보니 불편해서 UI 태그 다는게 맞을듯. 버튼 누르면 deselect 안되게.
if (hit.collider?.gameObject.CompareTag("Note") == true)
리팩토링 하면서 연산자 배운거 써먹어보려고 했는데
if (hit.collider != null && !hit.collider.gameObject.CompareTag("Button"))
이게 압도적으로 직관적일지도
근데 ?. 연산자 써보려니까 안됨.
if (!(hit.collider?.gameObject.CompareTag("Button") ?? false)) 이따구로 괴상하게 써야 한다고 함.
길어지더라도 그냥 직관적으로 쓰자
void DeselectObject()
{
selectedObject = null;
}
void SelectNewObject(GameObject obj)
{
selectedObject = obj;
Debug.Log("Selected: " + obj.name);
}
그리고 이것들.. 굳이 함수로 만들어야 되나?
public class ObjectSelector : MonoBehaviour
{
public GameObject selectedObject; // 현재 선택된 오브젝트
void Update()
{
// 마우스 왼쪽 버튼 클릭 감지
if (Input.GetMouseButtonDown(0))
SelectObject();
}
void SelectObject()
{
// 카메라에서 마우스 위치로 Ray 발사
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(mousePosition, Vector2.zero);
// Debug.Log($"Mouse Position: {mousePosition}");
// 버튼 누른게 아니라면 일단 선택 해제
if (hit.collider != null && !hit.collider.gameObject.CompareTag("Button"))
selectedObject = null;
// 블럭 클릭했으면 해당 블럭 선택
if (hit.collider != null && hit.collider.gameObject.CompareTag("Note"))
selectedObject = hit.collider.gameObject;
}
}
void ChangeBlock()
{
GameObject SelectedBlock = ObjectSelector.GetComponent<ObjectSelector>().selectedObject;
if (SelectedBlock == null)
{
Debug.Log("No selected block");
return;
}
Debug.Log("SelectedBlock: " + SelectedBlock.name);
SetBlockTypeAndInstantiate();
SetBlockDirection();
NewBlock.transform.position = SelectedBlock.transform.position;
ObjectSelector.GetComponent<ObjectSelector>().selectedObject = NewBlock;
Destroy(SelectedBlock);
}
void DeleteBlock()
{
GameObject SelectedBlock = ObjectSelector.GetComponent<ObjectSelector>().selectedObject;
if (SelectedBlock == null)
{
Debug.Log("No selected block. Delete failed.");
return;
}
Destroy(SelectedBlock);
}
이게 무슨 소리냐면,

현재는 노트의 중앙 좌표가 어디인지를 잘 모름.
그냥 그렸고, 그냥 넣어본거임.

figma에서 그린거 좌표가 이따구로 돼있으니까 그런거.
Pixels Per Unit도 신경써야 되는데 무지성으로 100 그대로 해놨음.
전혀 리듬게임 답지 않은 정밀도임.
벡터로 그려야되나?

캬 역시 갓그마 svg로도 export가 되네

근데 이거 찾아봤더니 못 쓰는듯 (지원을 끊어버림)
https://hotsunchip.tistory.com/3
꾸역꾸역 쓴 사람이 있긴 한데, 굳이 이렇게까지...

그냥 오브젝트로 그려버리고 프리팹으로 만들면 되지 않을까 싶음.
성능 문제가 일어날 수도 있겠지만 vertex들도 엄청 간단한데 그렇게까지 심각할까 생각이 듦.

너무나도 선명해졌다. 이게 벡터 그래픽이 아니면 뭐임

근데 이래버리면 또 다른 문제가 발생함.
여러 오브젝트를 겹쳐서 블럭을 만들었기 때문에, 마구잡이로 소팅 레이어 지정하고 우선순위 바꿔버리면
먼저 보여야 하는 오브젝트 a의 부모 오브젝트가 b의 자식 오브젝트 뒤에 보이게 되는 현상이 일어날 거임.

https://docs.unity3d.com/kr/2020.3/Manual/class-SortingGroup.html
다행히도 이런 기능이 있었다.

sorting group 붙이기 전

sorting group 붙인 이후
이제 나중에 맵 전체 정보를 저장하는 날이 오면
노트 순서에 따라 'Note' sorting group의 order in layer를 지정해주면 된다.

한 가지 문제가 발생.
음표 옆 화살표 부분이 소수점을 아무리 늘려도 좌표가 정확하게 맞아떨어지지가 않음.
루트 연산 때문일 것이라고 생각됨.
이렇게 되면 네모 2개로 만든 화살표가 모서리에서 살짝 어긋날 것임.

적당히 소수점 둘째자리까지만 맞게 조절해도 눈치 못 채겠지만,
뭔가 방법이 있지 않을까?
pivot을 조정한 square 스프라이트를 사용하면 될듯?

피벗을 BottomRight로 바꿔주고 y좌표를 0으로 설정하면

이제 아무리 확대해도 어긋나지 않는다

만드는데 개오래 걸림

판정 시스템을 위해 collider 3개를 심어놨고,
void SelectObject()
{
// 카메라에서 마우스 위치로 Ray 발사
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(mousePosition, Vector2.zero);
if (hit.collider != null)
{
ClickedObject = GetRootParent(hit.collider.gameObject);
// 버튼 누른게 아니라면 일단 선택 해제
if (!ClickedObject.CompareTag("Button"))
selectedObject = null;
// 블럭 클릭했으면 해당 블럭 선택
if (ClickedObject.CompareTag("Note"))
selectedObject = ClickedObject;
}
}
GameObject GetRootParent(GameObject obj)
{
while (obj.transform.parent != null)
{
obj = obj.transform.parent.gameObject;
}
return obj; // 최상위 부모 반환
}
이를 위해 ObjectSelector에서 클릭시 무조건 부모 오브젝트를 선택하게되는 걸로 바꿔두기도 했는데, 그게 문제가 아니라
이게 어떻게 작동할지 테스트해보지 않은 채로 막무가내로 만들었다는게 문제.
성능 비교도 안 해보고, 될지 안될지도 모르고, 그냥 judge_perfect collider에 걸리면 perfect 판정 하면 되겠지~ 라는 식으로 만들어버림.
이제 만약 내가 생각했던 거랑 살짝 달라져서 하나 바꾸면 나머지 싹 다 바꿔야 하니까 문제.
만들면서도 아 문제될 거 같은데 생각했는데 '일단 하자'는 마인드로 진행함.
작업의 우선순위를 정확히 파악하는게 중요한듯.