Unity/IGDC 입문 프로젝트

10.5 - 버튼 클릭 수정

말하는 닭 2023. 11. 7. 21:15

'10. 에러 수정'의 2번 문제 해결을 위한 글입니다. 

 

2번 문제가 저대로 수정이 된 줄 알았지만, 오히려 빈 곳을 눌러도 버튼이 안 사라지거나 인식이 안 되는 경우가 생겼습니다. 

게다가 기존 SiteClick.cs는 모든 공격타워에게 붙어서 계속 Update를 호출하기 때문에 비효율적이라 생각했습니다. 따라서 이 글에서는 SiteClick.cs를 대신할 새 스크립트를 하나 만들겁니다. 

 

우선은 모든 타워에 붙어있는 SiteClick 컴포넌트를 제거합니다. 

 

 

 

그리고 저는 SiteClick2 스크립트를 만들었습니다. 만든 스크립트는 MainCamera에 붙여줬습니다. 

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class SiteClick2 : MonoBehaviour
{
    // 띄울 모달 UI 오브젝트입니다
    public GameObject modal;
    // 모달 UI가의 활성 상태를 get, 모달의 Active를 들어온 값으로 set합니다.
    public bool Visible { get => modal.activeSelf; set => modal.SetActive(value); }

    // 모달 UI의 RectTransform 컴포넌트입니다
    private RectTransform modalRectTransform;
    // 어떤 site가 띄웠는지 저장하는 오브젝트의 컴포넌트입니다
    private PanelButtonClick panelButtonClick;

    void Start() {
        Visible = false;

        // 패널과 PanelButtonClick을 가져옵니다
        modalRectTransform = modal.GetComponent<RectTransform>();
        panelButtonClick = GameObject.Find("ButtonClickManager").GetComponent<PanelButtonClick>();
    }

    void Update() {
        if(Input.GetMouseButtonDown(0)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if(Visible) {
                if(!Physics.Raycast(ray, out var hit)) {
                    Invoke(nameof(SetModalVisible), 0.1f);
                }
                else if(hit.collider.CompareTag("AllyTower") && !IsPointerOverUIObject()) {
                    modalRectTransform.position = Camera.main.WorldToScreenPoint(hit.collider.transform.position) + Vector3.down * 40;
                    panelButtonClick.site = hit.collider.gameObject;
                }
                else {
                    SetModalVisible();
                }
            }
            else {
                if(Physics.Raycast(ray, out var hit)) {
                    modalRectTransform.position = Camera.main.WorldToScreenPoint(hit.collider.transform.position) + Vector3.down * 40;
                    panelButtonClick.site = hit.collider.gameObject;
                    SetModalVisible();
                }
            }
        }
    }

    // 모달을 활성화/비활성화 합니다.
    private void SetModalVisible() {
        Visible = !Visible;
    }

    // 하단 코드 출처: https://gameneo.medium.com/unity-raycast-%ED%84%B0%EC%B9%98-%EA%B5%AC%ED%98%84-%EC%8B%9C-ui-%EC%95%84%EB%9E%98-gameobject-%ED%84%B0%EC%B9%98-%EB%B0%A9%EC%A7%80%ED%95%98%EA%B8%B0-ea0f74534a41
    private bool IsPointerOverUIObject() {
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
        eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        return results.Count > 0;
    }
}

 

이전 SiteClick과는 달리 OnMouseDown 함수를 사용하지 않고 모두 raycast로 처리했습니다. 

그렇지만 Button은 UI이기 때문에 raycast가 감지되지 않습니다. 때문에 아래 사진과 같이 버튼이 타워 위에 있는 경우에 

버튼을 클릭해도 눌리는 것을 감지하기도 전에 다른 타워를 누른 것으로 인식해 버튼의 위치가 바뀝니다. 따라서 버튼 아래의 GameObject의 클릭을 방지하기 위해서 IsPointerOverUIObject 함수로 체크해줍니다. 

'업그레이드' 버튼이 타워 위에 있는 경우

 

 

 

또, 몬스터들의 도착지점에 있는 타워가 AllyTower 태그로 지정되어 있는 경우, 아래 사진과 같이 클릭을 할 수 있게 되어 버립니다. 이런 상황이 발생하지 않도록 끝에 있는 타워의 태그를 "Finish"로 변경해줍시다. 

 

태그가 변경되었으니, Enemy 스크립트의 도착 타워를 감지하는 OnTriggerEnter안에 AllyTower 태그인지 비교하는 값도 바꿔줘야겠죠.

if (other.gameObject.CompareTag("Finish")) {
    other.gameObject.GetComponent<AllyTower>().Hp--;
}

 AllyTower => Finish 로 변경해줍시다.