내일배움캠프 Unity 9기/TIL

[Unity/TIL] - QTE시스템, 프롬프트 만들기

song-ssi 2025. 6. 25. 11:09

QTE시스템

    public RectTransform needle;       // 바늘
    public float rotateSpeed = 90f;   // 초당 회전 속도(도)
    public KeyCode inputKey = KeyCode.Space;
    public Image successZone;
    public float minAngle = 120f; // 추후 매개변수로 변경
    public float maxAngle = 150f; // 추후 매개변수로 변경
    private float currentAngle = 0f; // 현재 각도. (바늘은 시작시 0도, 최대로 갔을 때 -180도 (반원))
    private bool isRunning;
    

    public void StartQTE()
    {
        currentAngle = 0f; // 현재 각도 초기화
        isRunning = true; // Update() 시작
        gameObject.SetActive(true); // QTE UI 화면에 뜸
        needle.localEulerAngles = Vector3.zero; // 바늘 각도 0도
        ShowSuccessZone(); // success zone 표시
    }

    private void EndQTE(bool success)
    {
        isRunning = false; // Update() 종료
        gameObject.SetActive(false); // QTE UI 화면에서 지움

        if (success) 
            Debug.Log("QTE 성공");
        else 
            Debug.Log("QTE 실패");
    }

    void Update()
    {
        if(!isRunning) return;
        
        currentAngle += rotateSpeed * Time.deltaTime; // 현재 각도 증가

        if(currentAngle >= 180f)
        {
            EndQTE(false);
            return;
        }

        needle.localEulerAngles = new Vector3(0, 0, -currentAngle); // 바늘 회전 적용 (시계방향)

        if (Input.GetKeyDown(inputKey))
        {
            if (currentAngle >= minAngle && currentAngle <= maxAngle)
                EndQTE(true);
            else
                EndQTE(false);
        }
    }

    void ShowSuccessZone()
    {
        float fill = (maxAngle - minAngle) / 360f;
        successZone.fillAmount = fill;
        
        // minAngle만큼 회전해서 시작 지점 맞추기
        successZone.rectTransform.localEulerAngles = new Vector3(0, 0, -minAngle);
    }

 

원래는 StartQTE() 에서 초기화값, 각도 증가, 바늘 회전 로직을 포함시키고

Update() 에서 StartQTE를 불러오는 형식으로 처리했었는데,

 

지금은 

StartQTE()는 초기화 값만 지정해주고

Update()에서 각도 증가, 바늘회전 로직을 실행하게 만들었다.

 

Update()에서 초기화 값을 매프레임마다 실행할 필요가 없다고 느꼈기 때문이다.


SetActive(false)의 영향

추가로 궁금했던 부분.

오브젝트가 SetActive(false) 되어있으면 update() 실행이 안되는건지?

아니면 눈으로 보이진 않지만 계속 update()가 실행되고있는건지?

 

 

🔹 GameObject 전체가 비활성화되면:

  • 그 오브젝트에 붙어있는 모든 컴포넌트의 Update(), FixedUpdate(), LateUpdate() 등 모든 루프 함수가 호출되지 않는다.
  • OnEnable()과 OnDisable()는 상태가 변경될 때 단 한번만 호출된다.

    🔸 단, **스크립트가 enabled = false**인 경우에는 해당 스크립트의 Update()만 호출되지 않고, 오브젝트 자체는 여전히 활성화된 상태다.
상태 Update()호출 여부
gameObject.SetActive(true) ✅ 호출됨
gameObject.SetActive(false) ❌ 호출 안 됨
script.enabled = false ❌ 호출 안 됨
script.enabled = true ✅ 호출됨

 

public class TestScript : MonoBehaviour
{
    void OnEnable()
    {
        Debug.Log("컴포넌트 활성화됨!");
    }

    void OnDisable()
    {
        Debug.Log("컴포넌트 비활성화됨!");
    }
}
gameObject.SetActive(false);  // GameObject 전체가 꺼짐 → OnDisable() 호출됨
gameObject.SetActive(true);   // 다시 켜짐 → OnEnable() 호출됨
//모든 컴포넌트가 비활성화됨
GetComponent<TestScript>().enabled = false;  // → OnDisable() 호출됨
GetComponent<TestScript>().enabled = true;   // → OnEnable() 호출됨
//컴포넌트만 껐다 켰을 때. GameObject는 활성상태
  • enabled 속성이 있는 컴포넌트(대표적으로 MonoBehaviour, Renderer, Collider, Animator 등)는
    GameObject와는 별개로 자신만 끄거나 켤 수 있다. 이때 해당 컴포넌트의 OnEnable()과 OnDisable()이 호출된다.

예:
GameObject는 켜져 있어도 → 스크립트만 꺼지면 Update()는 호출 안 돼
Renderer만 꺼지면 → 오브젝트는 존재하지만 렌더링되지 않음

 


 

Prompt 프롬프트

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class Prompt : Singleton<Prompt>
{
    private GameObject PromptPanel; // 프롬프트 패널 이미지
    private TMP_Text PromptText; // 프롬프트 텍스트

    private Queue<string> PromptQueue = new Queue<string>();
    private System.Action onDialogComplete;
    private bool isActive = false;

    private void Start()
    {
        PromptPanel = gameObject;
        PromptText = gameObject.GetComponentInChildren<TextMeshProUGUI>();
        
        PromptPanel.SetActive(false);
    }


    // ===================== 대화용 - 클릭시 넘어감 ============================
    
    public void ShowPrompt(string[] lines)
    {
        PromptQueue.Clear();
        foreach (var line in lines)
            PromptQueue.Enqueue(line);

        PromptPanel.SetActive(true);
        isActive = true;
        ShowNextLine();
    }

    private void ShowNextLine()
    {
        if (PromptQueue.Count > 0)
        {
            string nextLine = PromptQueue.Dequeue();
            PromptText.text = nextLine;
        }
        else
        {
            PromptPanel.SetActive(false);
            isActive = false;
            // onDialogComplete?.Invoke();
        }
    }

    private void Update()
    {
        if(!isActive) return;
        
        if(Input.GetMouseButtonDown(0) || Input.GetKeyDown(KeyCode.F))
            ShowNextLine();
    }

    // ===================== 알림용 - 일정시간 후 자동사라짐 ============================


    // 메시지와 시간 둘 다 받음
    public void ShowPrompt(string line, float dlaytime)
    {

        PromptPanel.SetActive(true); // 패널 보이게하기
        PromptText.text = line;
        StartCoroutine(HideAfterDelay(dlaytime));
    }


    private IEnumerator HideAfterDelay(float dlaytime)
    {
        yield return new WaitForSeconds(dlaytime);
        PromptPanel.SetActive(false);
        isActive = false;
        // onD
    }
}

 

 

사용예시

string[] lines = 
{
    "안녕하세요. 지금은",
    "프롬프트 테스트 중입니다",
    "잘 작동하나요?"            
};

// 클릭으로 다음 line으로 넘어갑니다. ShowPrompt(string[])
Prompt.Instance.ShowPrompt(lines);
// 지정한 시간(2f) 뒤에 자동으로 사라집니다. ShowPrompt(string, float)
Prompt.Instance.ShowPrompt("프롬프트 테스트입니다. 잘 작동 하나요?", 2f);