250305

lililllilillll·2025년 3월 5일

개발 일지

목록 보기
101/350

✅ What I did today


  • Project BCA


🎮 Project BCA


Sync terminal text with game progress

Delete mode

    public void pressButton(ref bool deleteMode, int deleteCount)
    {
        Debug.Log("Button Pressed");

        if (isAnimating) return; // 이미 애니메이션 중이면 실행 안 함
        isAnimating = true; // 애니메이션 시작

        // 아래로 이동 후 다시 원위치로 복귀 (Sequence 사용)
        Sequence sequence = DOTween.Sequence()
            .Append(transform.DOMoveY(originalPos.y - bounce_amount, press_duration).SetEase(Ease.InOutQuad)) // 아래로 이동
            .Append(transform.DOMoveY(originalPos.y, press_duration).SetEase(Ease.InOutQuad)) // 원위치 복귀
            .OnComplete(() => isAnimating = false); // 애니메이션 종료 후 isAnimating을 false로 변경

        sequence.Play();

        if (deleteCount <= 0)
        {
            terminalText.SetTemporalTerminalText("You can't delete anymore.");
            return;
        }

        deleteMode = !deleteMode;

        if (deleteMode)
            terminalText.SetTerminalText("Delete mode enabled");
        else
            terminalText.BackToOriginalText();
    }

버튼 눌렀는데 delete count가 0 이하면 delete mode 활성화 안 해주고
더 못 지운다는 출력 문구 잠시 띄움

    public void BackToOriginalTextWith(string context)
    {
        SetTerminalText(context + "\n" + originalText);
    }

    public void BackToOriginalText()
    {
        SetTerminalText(originalText);
    }

    public void SetTerminalText(string text)
    {
        if (typingCoroutine != null) StopCoroutine(typingCoroutine);
        terminalText.text = text;
        typingCoroutine = StartCoroutine(TypeText());
    }

    public void SetTemporalTerminalText(string text)
    {
        if (typingCoroutine != null) StopCoroutine(typingCoroutine);
        terminalText.text = text;
        typingCoroutine = StartCoroutine(TypeText(true));
    }

    IEnumerator TypeText(bool isTemporal = false)
    {
        terminalText.ForceMeshUpdate();
        TMP_TextInfo textInfo = terminalText.textInfo;
        int totalCharacters = textInfo.characterCount;

        terminalText.maxVisibleCharacters = 0;

        for (int i = 0; i < totalCharacters; i++)
        {
            terminalText.maxVisibleCharacters = i + 1;
            yield return new WaitForSeconds(typingSpeed);
        }

        if (isTemporal)
        {
            yield return new WaitForSeconds(1.5f);
            typingCoroutine = null;
            SetTerminalText(originalText);
        }
        else
        {
            typingCoroutine = null;
        }
    }

임시 출력 로직 때문에 코드가 살짝 읽기 어려워졌다

Game Over, Next stage

    /// <summary>
    /// 맨 처음 게임 시작할 때 호출되는 함수
    /// </summary>
    public void StartGame()
    {
        currentState = playerTurnState;
        terminalText.BackToOriginalText();
        board.SetBoard();
    }

    /// <summary>
    /// 다음 스테이지로 넘어가는 함수
    /// </summary>
    public void NextStage()
    {
        spotLight.enabled = false;
        currentState = waitingState;
        terminalText.SetTerminalText("Stage Clear!");

        stage++;

        // if (stage > 3)
        // {
        //     게임 엔딩 시퀀스 시작
        //     return;
        // }

        Invoke("ResetGame", 3f);
    }

    /// <summary>
    /// 게임 오버 후에 restart 눌렀을 때 호출
    /// </summary>
    public void RestartGame()
    {
        ResetGame();
        gameoverCanvas.gameObject.SetActive(false);
    }

    /// <summary>
    /// 새 게임을 시작하기 위해 정보를 초기화하는 함수
    /// </summary>
    public void ResetGame()
    {
        whiteTurn = true;
        currentState = playerTurnState;
        spotLight.enabled = true;

        board.ResetBoard();
        inputHandler.ResetInputHandler(stage);
        terminalText.BackToOriginalTextWith("Stage " + stage);
    }

    /// <summary>
    /// 종 치거나 게임 지면 호출되는 함수
    /// </summary>
    public void GameOver()
    {
        shooter.PlayShooterFootstep();
    }

각 시나리오에 맞게 터미널 출력

Intro sequence

public class IntroManager : MonoBehaviour
{
    private GameManager gameManager;
    private TerminalText terminalText;

    private CinemachineCamera current_vcam;
    private bool isWatchingMonitor = false;
    private float vcam_trans_time;
    [SerializeField] private CinemachineBrain brain;
    [SerializeField] private CinemachineCamera player_vacm;
    [SerializeField] private CinemachineCamera monitor_vcam;
    [SerializeField] private string[] introTexts; // bool이 true면 기존 텍스트와 함께 출력
    [SerializeField] private bool[] introTextsType; // true면 기존 텍스트와 함께 출력
    private int textIdx = 0;

    private void Start()
    {
        GameObject serviceLocator = GameObject.FindGameObjectWithTag("ServiceLocator");
        gameManager = serviceLocator.GetComponentInChildren<GameManager>();
        terminalText = serviceLocator.GetComponentInChildren<TerminalText>();

        vcam_trans_time = brain.DefaultBlend.BlendTime;

        StartIntro();
    }

    void Update()
    {
        if (isWatchingMonitor && Input.GetKeyDown(KeyCode.Return))
        {
            NextTerminalTexts();
        }
    }

    private void NextTerminalTexts()
    {
        if (textIdx == introTexts.Length)
        {
            isWatchingMonitor = false;
            ChangeVcam(player_vacm);
            gameManager.StartGame();
            Destroy(this);
            return;
        }

        if (introTextsType[textIdx])
            terminalText.AddTerminalText(introTexts[textIdx]);
        else
            terminalText.SetTerminalText(introTexts[textIdx]);

        textIdx++;
    }

    private void StartIntro()
    {
        // 천이 벗겨짐, 카메라가 모니터 앞으로 감, 모니터에 대사 출력, 엔터 누르면 다음 대사, 마지막 대사 뜬 후에 카메라가 돌아오면 게임 시작돼있음.

        Sequence seq = DOTween.Sequence();

        seq.AppendCallback(() => ChangeVcam(monitor_vcam));

        seq.AppendInterval(vcam_trans_time);
        seq.AppendCallback(() =>
        {
            isWatchingMonitor = true;
            NextTerminalTexts();
        });
    }

    private void ChangeVcam(CinemachineCamera vcam)
    {
        if (current_vcam != null)
            current_vcam.Priority = 0;

        vcam.Priority = 10;
        current_vcam = vcam;
    }
}

타임라인으로 하기엔 잠시 멈췄다가 플레이어의 입력에 맞추어 실행해야 하는 부분들이 있어서
전부 스크립트로 제어.

Unity 6여서 약간 달랐던 점

  • CinemachineVritualCamera가 아니라 CinemachineCamera로 선언해야 함
  • m_defulatBlend 뭐시기가 아니라 DefaultBlend.BlendTime로 가져와야 함.

introTextsType[]으로 같은 인덱스를 가진 introTexts[]의 텍스트가
이전 줄에 이어서 출력될지 새로 출력될지 결정

    /// <summary>
    /// 터미널에 출력되고 있던 텍스트 아래에 텍스트를 추가한다.
    /// </summary>
    public void AddTerminalText(string text)
    {
        if (typingCoroutine != null) StopCoroutine(typingCoroutine);
        int preLen = terminalText.text.Length;
        terminalText.text += "\n" + text;
        typingCoroutine = StartCoroutine(AddText(preLen));
    }

    /// <summary>
    /// 텍스트 추가 코루틴
    /// </summary>
    IEnumerator AddText(int preLen)
    {
        terminalText.ForceMeshUpdate();
        TMP_TextInfo textInfo = terminalText.textInfo;
        int totalCharacters = textInfo.characterCount;

        terminalText.maxVisibleCharacters = preLen;

        for (int i = preLen; i < totalCharacters; i++)
        {
            terminalText.maxVisibleCharacters = i + 1;
            yield return new WaitForSeconds(typingSpeed);
        }

        typingCoroutine = null;
    }

이전 줄에 이어서 출력하는 효과 메서드 및 코루틴.

코드 재사용하려고 하다간 코드가 더 복잡해질 것 같아서 AddText() 코루틴은 따로 작성.

profile
너 정말 **핵심**을 찔렀어

0개의 댓글