-
유니티 쉽게 배우는 디자인 패턴 - 전략 패턴 / Unity Design Pattern - Strategy Pattern유니티 2024. 8. 21. 16:51
전략패턴이란?
모드가 바뀔 때 사용하는 패턴입니다.
같은 함수를 호출하더라도 모드에 따라 다른 코드가 실행됩니다.
같은 움직임이라도 플레이어의 움직임, AI의 움직임, 적의 움직임에 따라 모드를 바꾸거나
총을 쏘더라도 권총, 산탄총, 소총에 따라 모드를 바꾸거나
공격을 하더라도 칼, 총, 지팡이에 따라 모드를 바꾸는 등에 용이하게 사용되고 있습니다.
전략패턴을 설명하는 다른 글들을 보면 주로 전투 관련으로 사용하고 있지만
저는 데이터 불러오기를 예시로 보여드리겠습니다.
로컬, 구글 스프레드 시트, 깃허브 지스트에서 텍스트를 불러오는 방법을 설명드리겠습니다.
같은 데이터 불러오기이지만 불러오는 방식이 다르기에 모드를 바꾸는 개념과 같아서 전략패턴이 적합합니다.
전략패턴 반영 전
DataController.cs
using System.Net.Http; using System.Threading.Tasks; using UnityEngine; public class DataController : MonoBehaviour { public enum ELoadType { Local, GoogleSheet, GithubGist } [SerializeField] ELoadType eLoadType; [SerializeField] TextAsset localTextAsset; string googleSheetUrl = "https://docs.google.com/spreadsheets/d/your_googlesheet_id/export?format=tsv"; string githubGistUrl = "https://gist.githubusercontent.com/your_github_name/your_gist_url"; public Task<string> LoadDataLocal() { return Task.FromResult(localTextAsset.text); } public async Task<string> LoadDataGoogleSheet() { using (HttpClient client = new HttpClient()) { try { string data = await client.GetStringAsync(googleSheetUrl); return data; } catch (HttpRequestException e) { Debug.LogError($"Request error: {e.Message}"); return null; } } } public async Task<string> LoadDataGithubGist() { using (HttpClient client = new HttpClient()) { try { string data = await client.GetStringAsync(githubGistUrl); return data; } catch (HttpRequestException e) { Debug.LogError($"Request error: {e.Message}"); return null; } } } async Task<string> LoadData() { string data = ""; if (eLoadType == ELoadType.Local) { data = await LoadDataLocal(); } else if (eLoadType == ELoadType.GoogleSheet) { data = await LoadDataGoogleSheet(); } else if (eLoadType == ELoadType.GithubGist) { data = await LoadDataGithubGist(); } Debug.Log(data); return data; } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { _ = LoadData(); } } }
스페이스 바를 누르면 eLoadType으로 분기된 모드에 따라
로컬, 구글 스프레드시트, 깃허브 지스트에서 데이터를 불러옵니다.
서버에서 불러오는 부분은 HTTP의 Get통신을 사용하여 보안 없이 가져오게 구성하였습니다.
비동기이기 때문에 Task를 사용하였습니다.
로컬에서 불러오기
프로젝트 창 우클릭 - Show in Explorer - TestText.txt 파일을 만듭니다.
텍스트 내용에 "안녕하세요 로컬입니다"를 적고 저장합니다.
LocalTextAsset에 방금만든 TestText.txt를 끌어다 놓아줍니다.
구글 스프레드 시트에서 불러오기
구글 스프레드 시트(https://docs.google.com/spreadsheets)에서 새로운 시트를 만들어 제목을 적어줍니다.
A1에 "안녕하세요 구글스프레드 시트입니다"를 적습니다.
오른쪽 위에 공유 클릭 - 일반 액세스를 링크가 있는 모든 사용자, 뷰어로 설정하고 완료를 누릅니다.
웹 URL "https://docs.google.com/spreadsheets/d/your_googlesheet_id/edit?gid=0#gid=0"을 복사하고
뒷부분 /edit~ 이후 부분을 /export?format=tsv 로 바꿔줍니다.
export 포맷을 tsv로 하는 것은 파일 - 다운로드 - 탭으로 구분된 값(.tsv)로 다운받은 결과와 동일합니다.
바꾼 URL "https://docs.google.com/spreadsheets/d/your_googlesheet_id/export?format=tsv"을
DataController의 googleSheetUrl에 붙혀넣습니다.
깃허브 지스트에서 불러오기
깃허브 지스트(https://gist.github.com)에 "안녕하세요 깃허브 지스트입니다"를 내용만 적어줍니다.
Create secret gist로 생성하여 링크로만 접근하도록 합니다.
Raw를 누르고 URL을 복사하고 githubGistUrl에 붙혀넣습니다.
로컬, 구글 스프레드 시트, 깃허브 지스트에서 불러올 준비가 모두 완료되었으면
플레이시 ELoadType을 바꾸며 스페이스 바를 누르면 각각에서 불러오게 됩니다.
하지만 위 코드는 같은 불러오기이지만 불러오는 방식이 다르기에
파이어베이스 등 다른 곳에서 불러오기를 더 추가하려고 할 때
enum도 추가해야 하고 if 분기도 써야하는 등 코드와 전역변수가 난잡해지게 됩니다.
전략패턴 반영 후
DataBase.cs
using System.Threading.Tasks; using UnityEngine; public abstract class DataBase : MonoBehaviour { public abstract Task<string> LoadData(); }
DataController.cs
using System.Threading.Tasks; using UnityEngine; public class DataController : MonoBehaviour { [SerializeField] DataBase dataBase; async Task<string> LoadData() { string data = await dataBase.LoadData(); Debug.Log(data); return data; } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { _ = LoadData(); } } }
DataLocal.cs
using System.Threading.Tasks; using UnityEngine; public class DataLocal : DataBase { [SerializeField] TextAsset localTextAsset; public override Task<string> LoadData() { return Task.FromResult(localTextAsset.text); } }
DataGoogleSheet.cs
using System.Net.Http; using System.Threading.Tasks; using UnityEngine; public class DataGoogleSheet : DataBase { string url = "https://docs.google.com/spreadsheets/d/your_googlesheet_id/export?format=tsv"; public override async Task<string> LoadData() { using (HttpClient client = new HttpClient()) { try { string data = await client.GetStringAsync(url); return data; } catch (HttpRequestException e) { Debug.LogError($"Request error: {e.Message}"); return null; } } } }
DataGithubGist.cs
using System.Net.Http; using System.Threading.Tasks; using UnityEngine; public class DataGithubGist : DataBase { string url = "https://gist.githubusercontent.com/your_github_name/your_gist_url"; public override async Task<string> LoadData() { using (HttpClient client = new HttpClient()) { try { string data = await client.GetStringAsync(url); return data; } catch (HttpRequestException e) { Debug.LogError($"Request error: {e.Message}"); return null; } } } }
DataBase는 추상클래스와 추상함수로 만들었고 데이터를 불러오는 부모클래스입니다.
비동기로 string을 반환하는 LoadData함수만 있습니다.
DataController는 스페이스 바를 누르면 현재 DataBase에 있는 LoadData를 호출하며
비동기로 반환된 텍스트를 출력합니다.
DataLocal, DataGoogleSheet, DataGithubGist는 모두 DataBase를 상속받으며
LoadData함수의 구현을 강제합니다.
각각의 구현방식은 모두 다르기 때문에 다른 코드와 전역변수로 구현이 됩니다.
만약 파이어베이스의 텍스트를 더 추가하고 싶다면 DataFirebase.cs를 만들고 DataBase를 상속받고
API키라던지 보안사항을 적용 후 불러오는 코드를 작성해 주시면 되겠습니다.
DataController 게임오브젝트를 만들어 DataController, DataLocal, DataGoogleSheet, DataGithubGist를
한 곳에 컴포넌트로 붙힌 뒤 재생합니다.
DataController의 매개변수 DataBase에 원하는 모드를 끌어다 넣어주고 각각 스페이스 바를 눌러 로그로 출력해봅니다.
새로 추가되는 모드(여기서는 불러오기)가 있을 때마다 기존 스크립트를 수정하지 않고 확장에 용이함을 알 수 있습니다.
마무리
같은 함수이지만 다른 코드를 적어야 할 때 분기처리 없이 전략패턴을 써서 깔끔한 코드로 작성이 됩니다.
모드를 바꿀 수 있다는 점이 매력적인 전략패턴이었습니다.
'유니티' 카테고리의 다른 글