using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UVC.UI.Buttons { /// /// Unity 기본 Toggle을 상속하지 않고 직접 토글 기능을 구현한 이미지 토글. /// isOn 상태에 따라 targetImage의 Sprite를 교체합니다. /// /// /// /// (); /// } /// /// void Start() /// { /// // 필드 연결 /// imageToggle.SetIsOnWithoutNotify(false); // 초기 상태 강제 설정(이벤트 없이) /// /// // targetImage 및 Sprite 지정 /// var so = new SerializedObject(imageToggle); // (직접 참조 가능하지만 예시로 표시) /// imageToggle.GetType(); // 의미 없는 호출(예시 컴파일 방지 목적) -> 실제 코드는 제거 가능 /// imageToggle.gameObject.name = "SampleImageToggle"; /// // 에디터에서는 인스펙터에서 설정하는 것이 일반적 /// /// // 리스너 등록 /// imageToggle.OnValueChanged.AddListener(OnToggleChanged); /// /// // 초기 텍스트 표시 /// if (stateText != null) /// stateText.text = imageToggle.isOn ? "ON" : "OFF"; /// } /// } /// /// private void OnToggleChanged(bool value) /// { /// if (stateText != null) /// stateText.text = value ? "ON" : "OFF"; /// } /// /// // 외부에서 호출하여 상태를 강제로 변경하는 메서드 예시 /// public void ForceOn() /// { /// imageToggle.isOn = true; /// } /// } /// ]]> /// /// public class ImageToggle : Selectable, IPointerClickHandler, ISubmitHandler { #region 이벤트 정의 /// /// 토글 값 변경 시(bool) 호출되는 UnityEvent. /// [System.Serializable] public class ImageToggleEvent : UnityEvent { } #endregion [Header("Sprite 교체 대상")] [Tooltip("Sprite를 교체할 대상 Image 컴포넌트")] [SerializeField] private Image targetImage; [Header("상태별 Sprite")] [Tooltip("토글이 켜짐(isOn=true) 상태일 때 사용할 Sprite")] [SerializeField] private Sprite onSprite; [Tooltip("토글이 꺼짐(isOn=false) 상태일 때 사용할 Sprite")] [SerializeField] private Sprite offSprite; [Header("이벤트")] [Tooltip("토글 값이 변경될 때 호출되는 이벤트")] public ImageToggleEvent OnValueChanged = new ImageToggleEvent(); [Header("초기 상태")] [Tooltip("시작 시 토글이 켜짐 상태인지 여부")] [SerializeField] private bool m_IsOn = false; /// /// 현재 토글 상태(읽기/쓰기). 값을 설정하면 시각과 이벤트를 갱신합니다. /// public bool isOn { get => m_IsOn; set => Set(value); } /// /// isOn 값을 이벤트 없이 변경합니다. /// public void SetIsOnWithoutNotify(bool value) { Set(value, false); } /// /// isOn 값을 이벤트 없이 강제로 설정하고 시각을 업데이트합니다. /// 풀에서 재사용 시 동일 값이어도 시각 갱신이 필요할 때 사용합니다. /// public void ForceSetIsOnWithoutNotify(bool value) { m_IsOn = value; UpdateVisual(); } /// /// 내부 설정 로직. 상태가 변경되면 비주얼과 콜백을 처리합니다. /// private void Set(bool value, bool sendCallback = true) { if (m_IsOn == value) // 동일 값이면 무시 return; m_IsOn = value; UpdateVisual(); if (sendCallback) { OnValueChanged.Invoke(m_IsOn); } } protected override void Start() { base.Start(); UpdateVisual(); } protected override void OnEnable() { base.OnEnable(); UpdateVisual(); } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); UpdateVisual(); } #endif /// /// 현재 isOn 상태에 맞게 targetImage Sprite를 교체. /// private void UpdateVisual() { if (targetImage == null) return; targetImage.sprite = m_IsOn ? onSprite : offSprite; } /// /// 토글 상태를 반전시킵니다. /// private void Toggle() { if (!IsActive() || !IsInteractable()) return; isOn = !isOn; // Set 통해 처리 } /// /// 마우스 클릭 처리(왼쪽 버튼). /// public void OnPointerClick(PointerEventData eventData) { if (eventData.button != PointerEventData.InputButton.Left) return; Toggle(); } /// /// 키보드/게임패드 Submit 처리. /// public void OnSubmit(BaseEventData eventData) { Toggle(); } } }