여러 게임들, 특히 타워디펜스나 턴제 게임을 보면 배속 기능이 포함되어 있습니다.
배속 기능은 참을성 없는 한국인에게 없어서는 안될 중요한 기능입니다.
이번 포스트에서는 유니티의 TimeScale을 이용하여 배속 기능을 구현해보도록 하겠습니다.
아래는 완성된 모습입니다.

* 아래 코드들은 MVC(Model-View-Controller) 패턴으로 이루어져 있습니다. 이 점 양해 부탁드립니다.
유니티 TimeScale은 유니티 내에서 시간의 흐름을 조절할 수 있는 변수입니다.
기본 값은 1이며, 값이 점점 커질수록 게임의 속도가 증가한다는 특징을 가지고 있습니다.
또한 TimeScale의 값을 변경하면, Time.deltaTime이나 Time.time, Time.fixedDeltaTime 모두 영향을 받습니다.
TimeScale을 코드로 변경하는 방법은 다음과 같습니다.
Time.timeScale = 1f; // 기본 속도
Time.timeScale = 0; // 멈춤
Time.timeScale = 2f; // 기본 속도의 2배
Time.timeScale을 접근하기 위해서는 using UnityEngine이 필요하며, Time이라는 네임스페이스나 변수가 없어야 정상적으로 작동합니다.
이를 이용해서 간단하게 유니티 버튼으로 배속 기능을 만들어 보도록 하겠습니다.
TimeScale은 위 코드와 비슷하게 바꿔줄 겁니다.
다만, 열거형(이하 enum)을 사용해서 조금 더 예쁘게 만들어 볼 겁니다.
스크립트를 하나 생성하고, 클래스 위에 enum 하나를 만들어 줄 겁니다.
public enum SpeedType
{
OneSpeed = 1,
TwoSpeed = 2,
ThreeSpeed = 3,
}
enum은 쉽게 말해서 상수에 이름을 붙여 사용할 수 있게 해주는 타입입니다. 기존에는 1,2,3이 무슨 숫자를 의미하는 건지 모르지만, OneSpeed, TwoSpeed 이런식으로 이름을 붙여 숫자의 의미를 만들어 줄 수 있습니다.
여기서 자신이 원하는 배속까지 값을 넣으시면 됩니다. 저는 1,2,3배를 구현하기 위해 OneSpeed, TwoSpeed, ThreeSpeed를 만들었습니다.
enum을 생성하였으면 실질적으로 TimeScale을 변경하는 코드를 작성해 보도록 하겠습니다.
public void ChangeSpeed()
{
switch (speedType)
{
case SpeedType.OneSpeed:
speedType = SpeedType.TwoSpeed;
break;
case SpeedType.TwoSpeed:
speedType = SpeedType.ThreeSpeed;
break;
case SpeedType.ThreeSpeed:
speedType = SpeedType.OneSpeed;
break;
}
Time.timeScale = (int)speedType;
}
}
ChangeSpeed라는 함수를 호출하여, 멤버변수인 speedType의 값을 통해 한 칸씩 값을 변경합니다.
만약 speedType이 OneSpeed라면, TwoSpeed로 변경하여 "나는 이제 2배속이다"를 알려주는 것이죠.
그리고 speedType의 값을 변경한 후 TimeScale의 값을 변경해 줍니다. 여기서 speedType을 int로 강제 형변환 하는 이유는, 어쨌든 speedType이 SpeedType이라는 enum형이기 때문에 강제형변환을 통해 enum이 갖고 있는 값으로 변환을 해줘야 합니다.
아래는 전체 코드입니다.
using UnityEngine;
namespace JMT.UISystem.GameSpeed
{
public enum SpeedType
{
OneSpeed = 1,
TwoSpeed = 2,
ThreeSpeed = 3,
}
public class GameSpeedModel
{
private SpeedType speedType = SpeedType.OneSpeed;
public SpeedType SpeedType => speedType;
public void ChangeSpeed()
{
switch (speedType)
{
case SpeedType.OneSpeed:
speedType = SpeedType.TwoSpeed;
break;
case SpeedType.TwoSpeed:
speedType = SpeedType.ThreeSpeed;
break;
case SpeedType.ThreeSpeed:
speedType = SpeedType.OneSpeed;
break;
}
Time.timeScale = (int)speedType;
}
}
}
이제 이 기능을 버튼에 연결해야 합니다. 저는 역할 분리를 위해 새로운 스크립트를 하나 더 생성했습니다.
유니티의 버튼 컴포넌트를 보시면, 아래 화면과 같이 OnClick이라는 이벤트가 있습니다.

OnClick에 대해 간단히 설명하자면, 이 버튼을 눌렀을 때 어떤 함수를 호출할 것인지 연결해주는 친구라고 보시면 됩니다.
이번 포스트에서는 이쪽에서 직접적으로 연결하지 않고, 코드를 통해 연결해 보도록 하겠습니다.
위 OnClick을 코드에서 불러오는 것은, 아래 코드처럼 작성하시면 됩니다,
speedButton.onClick.AddListener(연결할 메서드);
speedButton을 Button이라는 변수로 가져오면, onClick이라는 이벤트가 입력됩니다. AddListener를 통해 어떤 함수를 호출할 것인지 연결할 수 있으며, RemoveListener와 RemoveALlListeners를 통해 이벤트를 끊을 수도 있습니다.
RemoveListener와 RemoveALlListeners를 언제 쓸지 궁금해 하시는 분들을 위해서 설명드리자면, 혹시 모를 메모리 누수를 위해 사용합니다.
대부분의 경우에는 Unity가 정리를 해주긴 하지만, 스크립트가 갑자기 사라지거나 할 수 있기 때문에 OnDestroy에서 RemoveListener 해주는 습관을 들이는 것이 좋습니다.
본론으로 돌아와서, 저는 저 "연결할 메서드"에 액션을 실행해 주는 메서드를 연결해 두었습니다.
private void Awake()
{
speedButton.onClick.AddListener(HandleSpeedButtonEvent);
}
private void OnDestroy()
{
speedButton.onClick.RemoveListener(HandleSpeedButtonEvent);
}
private void HandleSpeedButtonEvent()
=> OnSpeedButtonEvent?.Invoke();
OnSpeedButtonEvent는 Action타입(delegate 타입) 변수입니다. OnSpeedButtonEvent?.Invoke(); 이런식으로 사용하게 되면, OnSpeedButtonEvent에 연결되어있는 함수들을 실행해 줍니다. (추후 포스트로 다루도록 하겠습니다.)
OnSpeedButtonEvent를 쓰는 이유는, 위 스크립트에서 버튼을 눌렀을 때 TimeScale을 변경하는 함수를 연결하지 않을 것이기 때문입니다. TimeScale을 변경하는 코드와 버튼 OnClick 이벤트를 가지고 있는 코드를 둘 다 가지고 있는 Controller에서 서로 연결해 줄 예정입니다.
아래는 전체 코드입니다.
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace JMT.UISystem.GameSpeed
{
public class GameSpeedView : MonoBehaviour
{
public event Action OnSpeedButtonEvent;
[SerializeField] private Button speedButton;
[SerializeField] private TextMeshProUGUI speedText;
private void Awake()
{
speedButton.onClick.AddListener(HandleSpeedButtonEvent);
}
private void OnDestroy()
{
speedButton.onClick.RemoveListener(HandleSpeedButtonEvent);
}
private void HandleSpeedButtonEvent()
=> OnSpeedButtonEvent?.Invoke();
public void ChangeSpeedText(SpeedType speedType)
{
speedText.text = (int)speedType + "x";
}
}
}
이제 GameSpeedController라는 스크립트에서 TimeScale 변경 스크립트와, 버튼 스크립트를 서로 연결해 줄 겁니다.
using UnityEngine;
namespace JMT.UISystem.GameSpeed
{
public class GameSpeedController : MonoBehaviour
{
[SerializeField] private GameSpeedView view;
private readonly GameSpeedModel model = new();
public int TimeScale => (int)model.SpeedType;
private void Awake()
{
view.OnSpeedButtonEvent += HandleSpeedButton;
view.ChangeSpeedText(model.SpeedType);
}
private void HandleSpeedButton()
{
model.ChangeSpeed();
view.ChangeSpeedText(model.SpeedType);
}
}
}
OnSpeedButtonEvent에서 함수를 연결하려면 +=를 작성해 주어야 합니다. +=을 작성한 후 원하는 메서드를 연결해 주면 됩니다.
이런식으로 버튼 같은 화면을 가지고 있는 View 스크립트, 게임의 로직을 담당하는 Model 스크립트, 그리고 이 둘을 연결해 주는 Controller 스크립트를 MVC 패턴이라고 합니다. 이렇게 한 기능을 세가지의 역할로 나누어서 만들게 되면, 후에 유지보수나 기능 추가를 할 때, 기능을 수정해야 할 때 매우 편리합니다.