using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 using UnityEngine.UI; // UI 기능을 불러올거에요 -> Image 컴포넌트를 // ============================================================ // CrosshairCursor — 마우스 커서 위치에 크로스헤어 이미지를 실시간 표시 // // [셋업 방법] // ① Canvas (Screen Space - Overlay 권장) 아래에 빈 오브젝트 생성 // ② Image 컴포넌트 추가 → 크로스헤어 스프라이트 할당 // (Raycast Target 체크 해제! → 클릭이 크로스헤어에 막히지 않도록) // ③ 이 스크립트를 같은 오브젝트에 추가 // ④ Inspector에서 crosshairImage에 방금 추가한 Image를 드래그 // // [기능] // - 시스템 마우스 커서 숨기고 크로스헤어 이미지로 대체 // - 차징 중 크기가 점점 커짐 (진행률에 비례) // - 차징 중 색상이 단계별로 변화 (흰색 → 주황 → 빨강) // - 포커스 잃으면 시스템 커서 복원 (에디터 편의) // ============================================================ public class CrosshairCursor : MonoBehaviour // 클래스를 선언할거에요 -> 크로스헤어 커서를 관리하는 { [Header("--- 크로스헤어 이미지 ---")] [SerializeField] private Image crosshairImage; // 변수를 선언할거에요 -> 크로스헤어 Image 컴포넌트를 [Header("--- 크기 설정 ---")] [SerializeField] private float baseSize = 40f; // 변수를 선언할거에요 -> 기본 크기(픽셀)를 [SerializeField] private float maxChargeSize = 70f; // 변수를 선언할거에요 -> 풀차징 시 최대 크기를 [Header("--- 색상 설정 ---")] [SerializeField] private Color normalColor = Color.white; // 변수를 선언할거에요 -> 기본 상태 색상을 [SerializeField] private Color chargingColor = new Color(1f, 0.6f, 0f, 1f); // 변수를 선언할거에요 -> 차징 중간 색상(주황)을 [SerializeField] private Color maxChargeColor = new Color(1f, 0.2f, 0f, 1f); // 변수를 선언할거에요 -> 풀차징 색상(빨강)을 [Header("--- 시스템 커서 ---")] [SerializeField] private bool hideSystemCursor = true; // 변수를 선언할거에요 -> 시스템 커서 숨김 여부를 [Header("--- 참조 (비워두면 자동 탐색) ---")] [SerializeField] private PlayerAttack playerAttack; // 변수를 선언할거에요 -> 차징 상태 확인용 공격 스크립트를 private RectTransform _rect; // 변수를 선언할거에요 -> 크로스헤어 RectTransform을 private Canvas _canvas; // 변수를 선언할거에요 -> 부모 Canvas를 // ── 초기화 ────────────────────────────────────────────── private void Awake() // 함수를 실행할거에요 -> 초기화를 { if (crosshairImage != null) // 조건이 맞으면 실행할거에요 -> 이미지가 있다면 { _rect = crosshairImage.GetComponent(); // 가져올거에요 -> RectTransform을 _canvas = crosshairImage.GetComponentInParent(); // 가져올거에요 -> 부모 Canvas를 // 초기 크기 설정 _rect.sizeDelta = new Vector2(baseSize, baseSize); // 설정할거에요 -> 기본 크기를 // Raycast Target 꺼두기 (크로스헤어가 클릭을 가로채지 않도록) crosshairImage.raycastTarget = false; // 끌거에요 -> 레이캐스트 타겟을 } } private void Start() // 함수를 실행할거에요 -> 시작 시 { if (hideSystemCursor) Cursor.visible = false; // 숨길거에요 -> 시스템 커서를 // 자동 탐색 if (playerAttack == null) // 조건이 맞으면 실행할거에요 -> 참조가 비어있다면 playerAttack = FindObjectOfType(); // 찾을거에요 -> 씬에서 PlayerAttack을 } // ── 매 프레임 갱신 ────────────────────────────────────── private void Update() // 함수를 실행할거에요 -> 매 프레임 { if (crosshairImage == null || _rect == null) return; // 중단할거에요 -> 이미지 없으면 // 1. 마우스 위치로 이동 MoveToMouse(); // 실행할거에요 -> 위치 갱신을 // 2. 차징 시각 피드백 UpdateChargeFeedback(); // 실행할거에요 -> 크기/색상 갱신을 } // ── 위치 이동 ────────────────────────────────────────── private void MoveToMouse() // 함수를 선언할거에요 -> 크로스헤어를 마우스 위치로 이동하는 { if (_canvas == null) return; // 중단할거에요 -> 캔버스 없으면 if (_canvas.renderMode == RenderMode.ScreenSpaceOverlay) // Overlay 모드 { // Overlay: 스크린 좌표 그대로 사용 _rect.position = Input.mousePosition; // 이동할거에요 -> 마우스 위치로 } else // Camera / World Space 모드 { RectTransformUtility.ScreenPointToLocalPointInRectangle( _canvas.transform as RectTransform, Input.mousePosition, _canvas.worldCamera, out Vector2 localPos ); // 변환할거에요 -> 스크린→캔버스 로컬 좌표로 _rect.localPosition = localPos; // 이동할거에요 -> 변환된 위치로 } } // ── 차징 시각 피드백 (크기 + 색상) ────────────────────── private void UpdateChargeFeedback() // 함수를 선언할거에요 -> 차징 피드백을 갱신하는 { bool charging = (playerAttack != null && playerAttack.IsCharging); // 판단할거에요 -> 차징 중인지 if (charging) // 차징 중이면 { float progress = playerAttack.ChargeProgress; // 가져올거에요 -> 진행률(0~1)을 // ── 크기 보간 ── float size = Mathf.Lerp(baseSize, maxChargeSize, progress); // 보간할거에요 -> 진행률에 따른 크기를 _rect.sizeDelta = new Vector2(size, size); // 적용할거에요 -> 크기를 // ── 색상 보간 (2단 그라데이션) ── Color color; if (progress < 0.5f) // 전반부 color = Color.Lerp(normalColor, chargingColor, progress * 2f); // 흰색 → 주황 else // 후반부 color = Color.Lerp(chargingColor, maxChargeColor, (progress - 0.5f) * 2f); // 주황 → 빨강 crosshairImage.color = color; // 적용할거에요 -> 색상을 } else // 차징 아닐 때 { _rect.sizeDelta = new Vector2(baseSize, baseSize); // 복원할거에요 -> 기본 크기로 crosshairImage.color = normalColor; // 복원할거에요 -> 기본 색상으로 } } // ── 포커스/비활성화 시 커서 복원 ───────────────────────── private void OnDisable() // 함수를 실행할거에요 -> 비활성화 시 { Cursor.visible = true; // 복원할거에요 -> 시스템 커서를 (에디터/게임 종료 시 안전장치) } private void OnApplicationFocus(bool hasFocus) // 함수를 실행할거에요 -> 창 포커스 변경 시 { if (hideSystemCursor) // 커서 숨김 모드라면 Cursor.visible = !hasFocus; // 포커스 잃으면 보이게, 돌아오면 숨기게 } // ── 외부 연결 API (DungeonSceneSetup 등에서 런타임 설정) ── /// 런타임에서 PlayerAttack 참조를 설정합니다. public void SetPlayerAttack(PlayerAttack attack) // 함수를 선언할거에요 -> 런타임 참조 설정을 { playerAttack = attack; // 저장할거에요 -> 공격 스크립트 참조를 } }