반응형
async/await와 코루틴의 주요 차이점
1. 반환값 처리
// 코루틴 - 직접적인 반환값을 받을 수 없음
IEnumerator LoadDataCoroutine()
{
yield return new WaitForSeconds(1f);
string data = "데이터";
// 반환값을 직접 받을 수 없어서 다른 변수나 콜백으로 처리해야 함
}
// async - 직접 반환값을 받을 수 있음
async Task<string> LoadDataAsync()
{
await Task.Delay(1000);
return "데이터"; // 직접 반환 가능
}
2. 예외처리
// 코루틴 - try-catch 사용 불가
IEnumerator ErrorCoroutine()
{
yield return new WaitForSeconds(1f);
throw new Exception("에러 발생!"); // 예외 처리가 어려움
}
// async - try-catch 사용 가능
async Task ErrorHandlingAsync()
{
try
{
await Task.Delay(1000);
throw new Exception("에러 발생!");
}
catch (Exception e)
{
Debug.LogError(e); // 정상적으로 예외 처리
}
}
3. Unity 엔진과의 통합
// 코루틴 - Unity의 Time.timeScale 영향 받음
IEnumerator WaitCoroutine()
{
yield return new WaitForSeconds(1f); // timeScale이 0이면 멈춤
}
// async - Time.timeScale 영향 받지 않음
async Task WaitAsync()
{
await Task.Delay(1000); // timeScale과 무관하게 실행
}
4.취소와 제어
// 코루틴 - 간단한 취소
private Coroutine currentCoroutine;
void StartAndStopCoroutine()
{
currentCoroutine = StartCoroutine(SomeCoroutine());
StopCoroutine(currentCoroutine); // 쉽게 취소 가능
}
// async - CancellationToken 필요
private CancellationTokenSource cts = new CancellationTokenSource();
async Task CancellableAsync()
{
try
{
await Task.Delay(1000, cts.Token);
}
catch (OperationCanceledException)
{
Debug.Log("작업 취소됨");
}
}
5. 주요 사용 사례
// 코루틴 - Unity 관련 작업에 적합
IEnumerator AnimationCoroutine()
{
yield return new WaitForSeconds(1f);
transform.position += Vector3.up;
yield return new WaitForEndOfFrame();
// Unity의 프레임 기반 작업에 좋음
}
// async - 데이터 처리, 네트워크 작업에 적합
async Task LoadPlayerDataAsync()
{
var data = await GetDataFromServerAsync();
await SaveToFileAsync(data);
// 복잡한 비동기 작업 체이닝에 좋음
}
권장하는 방식
코루틴
Unity의 물리/애니메이션 관련 작업
프레임 기반 로직
간단한 시간 지연
async/await
파일 입출력
네트워크 통신
복잡한 비동기 로직
예외 처리가 필요한 경우
반환값이 필요한 경우
이러한 차이가 발생하는 이유
코루틴의 동작 방식
IEnumerator ExampleCoroutine()
{
Debug.Log("시작");
yield return new WaitForSeconds(1f);
Debug.Log("1초 후");
}
- 코루틴은 유니티 엔진이 직접 관리하는 특별한 반복자(Iterator) 패턴이다
- 매 프레임 마다 Unity 엔진이 yield return 지점까지 실행하고 제어를 가져간다
- 유니티의 게임 루프와 밀접하게 연결되어 있다
- Time.timescale의 영향을 받고, waitforseconds 같은 유니티 전용 기능을 사용할 수 있다
async/await의 동작 방식
async Task ExampleAsync()
{
Debug.Log("시작");
await Task.Delay(1000);
Debug.Log("1초 후");
}
- async/await는 C# 언어 자체의 기능으로, .NET 런타임이 관리한다
- 상태 머신(State Machine)으로 컴파일되어 비동기 작업을 관리한다
- Unity 엔진과 독립적으로 동작을 한다
- try-catch나 반환값 같은 C#의 일반적인 기능들을 모두 사용할 수 있다
이러한 구현 방식의 차이 때문에
코루틴은 유니티에 종속적이지만 유니티 기능을 잘 활용할 수 있고
async/await는 더 범용적이고 강력하지만 유니티의 특수한 기능들과는 거리가 있다
활용예시
게임 저장 시스템
// 비동기 방식 (async/await)
public class SaveSystemAsync
{
public async Task SaveGameAsync(GameData data)
{
try
{
string json = JsonUtility.ToJson(data);
string path = Path.Combine(Application.persistentDataPath, "save.json");
// 파일 저장 (무거운 I/O 작업)
await File.WriteAllTextAsync(path, json);
Debug.Log("게임 저장 완료!");
// 클라우드 백업 (네트워크 작업)
await UploadToCloudAsync(json);
Debug.Log("클라우드 백업 완료!");
}
catch (Exception e)
{
Debug.LogError($"저장 실패: {e.Message}");
}
}
}
// 사용 예시
public class GameManager : MonoBehaviour
{
private SaveSystemAsync saveSystem;
public async void OnSaveButtonClick()
{
var saveButton = GetComponent<Button>();
saveButton.interactable = false; // 저장 중 버튼 비활성화
await saveSystem.SaveGameAsync(currentGameData);
saveButton.interactable = true; // 저장 완료 후 버튼 활성화
}
}
SaveSystemAsync의 장점
- 파일 I/O 작업을 비동기로 처리하여 게임이 멈추지 않음
- 에러 처리가 깔끔함
- 클라우드 저장같은 네트워크 작업과 연계가 쉬움
스킬 시스템
public class SkillSystem : MonoBehaviour
{
// 코루틴 방식 - 시각적 효과와 타이밍이 중요한 경우
public IEnumerator CastFireballCoroutine()
{
// 캐스팅 시작 이펙트
var castingEffect = Instantiate(castingEffectPrefab, transform.position, Quaternion.identity);
// 캐스팅 시간 (2초)
float castTime = 2f;
float elapsed = 0f;
while (elapsed < castTime)
{
// 캐스팅 게이지 업데이트
float castProgress = elapsed / castTime;
UpdateCastingBar(castProgress);
elapsed += Time.deltaTime;
yield return null;
}
// 발사 이펙트
var fireball = Instantiate(fireballPrefab, spawnPoint.position, transform.rotation);
fireball.GetComponent<Rigidbody>().AddForce(transform.forward * fireballSpeed);
// 마나 소모
playerStats.UseMana(30);
// 쿨다운 시작
yield return new WaitForSeconds(5f);
// 스킬 다시 사용 가능
EnableSkillButton();
}
// async/await 방식 - 서버 통신이 필요한 경우
public async Task ValidateAndCastSkillAsync(int skillId)
{
try
{
// 서버에 스킬 사용 가능 여부 확인
var response = await serverClient.CheckSkillAvailabilityAsync(skillId);
if (response.canUseSkill)
{
// 서버에 스킬 사용 알림
await serverClient.NotifySkillUseAsync(skillId);
// 실제 스킬 효과 실행 (코루틴)
StartCoroutine(CastFireballCoroutine());
}
else
{
Debug.Log($"스킬 사용 불가: {response.reason}");
}
}
catch (Exception e)
{
Debug.LogError($"스킬 사용 중 에러 발생: {e.Message}");
}
}
}
SkillSystem의 특징
- 코루틴: 시각적 효과와 게임 타이밍에 맞춘 처리
- async/await: 서버 통신과 같은 네트워크 작업 처리
- 두 방식의 적절한 조합 사용
스킬 시스템을 보다 의문점이 들었다
라인 게임이라면 굳이 코루틴으로 만들 필요가 있을까?
통합할 순 없을까? 라는 의문점이다
온라인스킬시스템
public class OnlineSkillSystem : MonoBehaviour
{
public async Task CastFireballAsync()
{
try
{
// 1. 서버에 스킬 사용 가능 여부 확인 (쿨타임, 마나 등)
var canUseSkill = await serverClient.CheckSkillAvailableAsync("Fireball");
if (!canUseSkill)
{
Debug.Log("스킬을 현재 사용할 수 없습니다.");
return;
}
// 2. 스킬 사용 시작을 서버에 알림
await serverClient.NotifySkillStartAsync("Fireball");
// 3. 클라이언트의 시각적 효과 처리
var castingEffect = Instantiate(castingEffectPrefab);
// 4. 캐스팅 시간 (2초) 처리
float castTime = 2.0f;
float startTime = Time.time;
while (Time.time - startTime < castTime)
{
float progress = (Time.time - startTime) / castTime;
UpdateCastingBar(progress);
await Task.Yield(); // 다음 프레임까지 대기
}
// 5. 스킬 발동 효과
var fireball = Instantiate(fireballPrefab, spawnPoint.position, transform.rotation);
fireball.GetComponent<Rigidbody>().AddForce(transform.forward * fireballSpeed);
// 6. 스킬 완료를 서버에 알림
await serverClient.NotifySkillCompletedAsync("Fireball", transform.position, transform.rotation);
}
catch (Exception e)
{
Debug.LogError($"스킬 사용 중 오류 발생: {e.Message}");
// 서버에 스킬 취소 알림
await serverClient.NotifySkillCancelledAsync("Fireball");
}
}
// UI 버튼에서 호출
public async void OnFireballButtonClick()
{
var skillButton = GetComponent<Button>();
skillButton.interactable = false;
await CastFireballAsync();
// 서버에서 쿨타임 정보를 받아와서 버튼 상태 업데이트
var cooldownInfo = await serverClient.GetSkillCooldownAsync("Fireball");
UpdateSkillButtonCooldown(cooldownInfo);
}
}
통합 장점
- 서버 통신이 많은 온라인 게임의 특성상 일관된 비동기 패턴 사용이 유지보수에 더 좋습니다
- 예외 처리가 더 깔끔합니다
- 서버와의 통신 실패 시 롤백 처리가 더 쉽습니다
통합 주의할 점
- Unity의 Transform, Rigidbody 등 물리/애니메이션 관련 작업은 메인 스레드에서 해야 합니다
- Time.time 같은 Unity API는 메인 스레드에서만 접근 가능합니다
- 프레임 단위의 업데이트가 필요한 경우 Task.Yield()를 사용해야 합니다
여기서 개인적인 궁금증이 생겼는데,
poe2나 원신 같은 게임은 혼자할 땐 게임이 일시정지가 가능하지만
파티를 한다면 일시정지가 불가능하다
이러한 게임들을 생각한 구현 방식
public class SkillSystem : MonoBehaviour
{
private bool isOnlineMode = false;
public void SetOnlineMode(bool isOnline)
{
isOnlineMode = isOnline;
}
// 스킬 실행을 위한 통합 진입점
public void CastSkill()
{
if (isOnlineMode)
{
// 온라인 모드: async/await 사용
_ = CastSkillAsync();
}
else
{
// 오프라인 모드: 코루틴 사용
StartCoroutine(CastSkillCoroutine());
}
}
// 온라인용 async 구현
private async Task CastSkillAsync()
{
try
{
// 서버와 동기화가 필요한 온라인 로직
await serverClient.CheckSkillAvailableAsync();
await serverClient.NotifySkillStartAsync();
// 시각 효과 등 공통 로직
await PlaySkillEffectAsync();
await serverClient.NotifySkillCompletedAsync();
}
catch (Exception e)
{
Debug.LogError($"온라인 스킬 사용 실패: {e.Message}");
}
}
// 오프라인용 코루틴 구현
private IEnumerator CastSkillCoroutine()
{
if (Time.timeScale == 0) // 일시정지 상태 체크
{
yield break;
}
// 로컬에서만 처리되는 오프라인 로직
if (!CheckLocalSkillAvailable())
{
yield break;
}
// 시각 효과 등 공통 로직
yield return StartCoroutine(PlaySkillEffectCoroutine());
// 로컬 상태 업데이트
UpdateLocalSkillState();
}
}
// 게임 매니저에서 모드 전환 처리
public class GameManager : MonoBehaviour
{
private SkillSystem skillSystem;
public void OnPartyJoined()
{
// 파티 참가시 온라인 모드로 전환
skillSystem.SetOnlineMode(true);
Time.timeScale = 1f; // 강제로 일시정지 해제
DisablePauseMenu(); // 일시정지 메뉴 비활성화
}
public void OnPartyLeft()
{
// 파티 퇴장시 오프라인 모드로 전환
skillSystem.SetOnlineMode(false);
EnablePauseMenu(); // 일시정지 메뉴 활성화
}
public void OnEscKeyPressed()
{
if (!skillSystem.isOnlineMode)
{
Time.timeScale = Time.timeScale == 0 ? 1f : 0f; // 일시정지 토글
UpdatePauseMenu();
}
}
}
싱글플레이 때는 코루틴으로 동작하면서 일시정지가 가능하게
파티 참가시 자동으로 async 방식으로 전환되도록 하였다
반응형
'유니티 > 문법' 카테고리의 다른 글
[Unity] 유니티 C#) async/await (0) | 2025.01.21 |
---|---|
[Unity] 유니티 C#) Property ( 프로퍼티 ) (0) | 2025.01.20 |
[Unity] 유니티C#) Struct ( 구조체 ) (1) | 2025.01.17 |
[Unity] 유니티 C#) Casting ( 형변환 ) (0) | 2025.01.16 |
[Unity] 유니티 C#) try-catch문 ( 예외처리 ) (1) | 2025.01.15 |