Projext/Assets/02_Scripts/UI/HUD/CrosshairCursor.cs

157 lines
8.0 KiB
C#
Raw Normal View History

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>(); // 가져올거에요 -> RectTransform을
_canvas = crosshairImage.GetComponentInParent<Canvas>(); // 가져올거에요 -> 부모 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>(); // 찾을거에요 -> 씬에서 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 등에서 런타임 설정) ──
/// <summary>런타임에서 PlayerAttack 참조를 설정합니다.</summary>
public void SetPlayerAttack(PlayerAttack attack) // 함수를 선언할거에요 -> 런타임 참조 설정을
{
playerAttack = attack; // 저장할거에요 -> 공격 스크립트 참조를
}
}