Unity) [유니티 3D] 게임 진입까지 구조
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneArgs { }
public class GameSceneArgs : SceneArgs
{
public int selectedCharacterId;
}
public class App : MonoBehaviour
{
public enum eSceneType
{
App, Title, Loading, Lobby, Game
}
private TitleMain titleMain;
private void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
private void Start()
{
DataManager.instance.LoadCharacterData();
DataManager.instance.LoadAssetData();
this.ChangeScene(eSceneType.Title);
}
public void ChangeScene(eSceneType sceneType, SceneArgs args = null)
{
Debug.LogFormat("ChangeScene: {0}", sceneType);
var oper = SceneManager.LoadSceneAsync(sceneType.ToString());
switch (sceneType)
{
case eSceneType.Title:
//비동기 로드
oper.completed += (obj) => {
//씬로드가 완료됨 (메모리에 다 올라감)
//인스턴스에 접근 가능
this.titleMain = GameObject.FindObjectOfType<TitleMain>();
this.titleMain.uiDirector.onClick = () => {
this.ChangeScene(eSceneType.Loading);
};
this.titleMain.Init();
};
break;
case eSceneType.Loading:
oper.completed += (obj) => {
var loadingMain = GameObject.FindObjectOfType<LoadingMain>();
loadingMain.onComplete = () =>
{
this.ChangeScene(eSceneType.Lobby);
};
loadingMain.Init();
};
break;
case eSceneType.Lobby:
oper.completed += (obj) =>
{
var lobbyMain = GameObject.FindObjectOfType<LobbyMain>();
lobbyMain.onClickStartGame = (selectedId) =>
{
var args = new GameSceneArgs() { selectedCharacterId = selectedId };
this.ChangeScene(eSceneType.Game, args);
};
lobbyMain.Init();
};
break;
case eSceneType.Game:
oper.completed += (obj) =>
{
var gameSceneArgs = args as GameSceneArgs;
var gameMain = GameObject.FindObjectOfType<GameMain>();
gameMain.Init(gameSceneArgs.selectedCharacterId);
};
break;
}
}
}
- as 연산자 : 형변환이 가능하면 변환 값을 반환하고 그렇지 않으면 null 값을 반환
- is 연산자 : 형변환 가능 여부를 boolean 타입으로 반환
-> 실제 하향캐스팅에서 사용된 예제를 확인할 필요가 있음
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TitleMain : MonoBehaviour
{
public UITitleDirector uiDirector;
void Start()
{
}
public void Init()
{
this.uiDirector.Init();
this.uiDirector.FadeIn();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class UITitleDirector : MonoBehaviour
{
public Image dim;
public Text txtVersion; //0.0.1
public Button btn;
public System.Action onClick;
public void Init()
{
this.txtVersion.text = string.Format("v{0}", Application.version);
var color = this.dim.color;
color.a = 1;
this.dim.color = color;
this.dim.raycastTarget = true;
this.btn.onClick.AddListener(() =>
{
this.btn.interactable = false;
this.dim.raycastTarget = false;
this.onClick();
});
this.btn.gameObject.SetActive(false);
}
public void FadeIn()
{
DOTween.ToAlpha(() => this.dim.color, x => this.dim.color = x, 0, 1f).SetEase(Ease.OutQuad)
.onComplete = () =>
{
Debug.Log("FadeIn Complete!");
this.btn.gameObject.SetActive(true);
this.dim.raycastTarget = false;
};
}
public void FadeOut()
{
DOTween.ToAlpha(() => this.dim.color, x => this.dim.color = x, 1, 1f).SetEase(Ease.OutQuad)
.onComplete = () =>
{
Debug.Log("FadeOut Complete!");
};
}
}
- interactable : 버튼의 상호작용 가능여부를 boolean타입으로 지정한다.
- DoTween 네임스페이스를 참조하여 메서드를 호출할 수 있는데 순차적으로 변화하는 값을 구현할 수 있다. 현재 코드에서는 Color의 알파값을 Ease그래프에 따라 순차적으로 변화시킨다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LoadingMain : MonoBehaviour
{
public UILoadingDirector director;
public System.Action onComplete;
public void Init()
{
AssetManager.instance.onProgress = (per) =>
{
if (per >= 1)
{
this.onComplete();
}
this.director.UpdateUI(per);
};
AssetManager.instance.LoadAllAssets();
}
}
- AssetManager의 onProgrss 대리자를 정의하고 있는데 해당 대리자는 AssetManager 클래스에서 IEnumerator 인터페이스를 통해 비동기적으로 실행되고 있으며 해당 클래스의 메서드 내부에서 데이터의 갯수만큼 실행되어 전부 로딩이 될 때까지 비동기적으로 데이터를 불러오는 기능을 수행한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class UILoadingDirector : MonoBehaviour
{
public Slider slider;
public TMP_Text txtPer;
public void UpdateUI(float per)
{
this.slider.value = per;
this.txtPer.text = string.Format("{0:0.##}%", per * 100f); //98.32, 100%
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LobbyMain : MonoBehaviour
{
public UILobbyDirector director;
public System.Action<int> onClickStartGame;
private int selectedCharacterId;
public void Init()
{
this.director.Init();
this.director.btn.onClick.AddListener(() => {
this.onClickStartGame(this.director.selectedId);
});
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UILobbyDirector : MonoBehaviour
{
public Button btn;
public UICharacterSlot[] uiCharacterSlots;
private int selectedIdx = 0;
public int selectedId = -1;
public void Init()
{
var list = DataManager.instance.GetCharacterDatas();
for (int i = 0; i < list.Count; i++)
{
//프리팹(clone) 인스턴화
//슬롯에 넣기
var data = list[i];
var prefabName = string.Format("UI{0}", data.prefab_name);
var prefab = AssetManager.instance.GetPrefab(prefabName);
var go = Instantiate(prefab);
this.uiCharacterSlots[i].Init(data.id, go);
}
//다선택 취소 하기
this.UnSelectAllSlots();
//이벤트 붙이기
for (int i = 0; i < this.uiCharacterSlots.Length; i++)
{
var idx = i;
var slot = uiCharacterSlots[idx];
slot.btn.onClick.AddListener(() => {
Debug.LogFormat("selected slot id :{0}, idx:{1}", slot.id, idx);
this.selectedId = slot.id;
this.selectedIdx = idx;
this.UnSelectAllSlots();
this.SelectSlot(this.selectedIdx);
});
}
this.selectedId = list[0].id;
this.selectedIdx = 0;
this.SelectSlot(this.selectedIdx);
}
private void SelectSlot(int idx)
{
this.uiCharacterSlots[this.selectedIdx].Select();
}
private void UnSelectAllSlots()
{
foreach (var slot in this.uiCharacterSlots)
{
slot.UnSelect();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UICharacterSlot : MonoBehaviour
{
public Transform pivot; //character가 부착될 부모
public int id;
public GameObject checkGo;
public Button btn;
private void Awake()
{
}
public void Init(int id, GameObject go)
{
this.id = id;
go.transform.SetParent(this.pivot);
go.transform.localPosition = Vector3.zero;
go.transform.localScale = Vector3.one;
}
public void Select()
{
this.checkGo.SetActive(true);
}
public void UnSelect()
{
this.checkGo.SetActive(false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameMain : MonoBehaviour
{
public GameObject heroShellPrefab; //prefab
public void Init(int selectedCharacterId)
{
Debug.LogFormat("[GameMain] selectedCharacterId: {0}", selectedCharacterId);
this.CreateHero(selectedCharacterId);
}
private void CreateHero(int id)
{
//껍데기 만들기
var shellGo = Instantiate(this.heroShellPrefab);
var hero = shellGo.GetComponent<Hero>();
//모델
var data = DataManager.instance.GetCharacterData(id);
var prefab = AssetManager.instance.GetPrefab(data.prefab_name);
var modelGo = Instantiate(prefab);
hero.Init(modelGo);
}
}
- 프리팹을 통해 바로 원하는 오브젝트를 인스턴스화 하는 것이 아닌 빈 오브젝트를 먼저 인스턴스화 하고 해당 오브젝트에 스크립트를 붙여 위치, 크기조절을 효율적으로 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Hero : MonoBehaviour
{
private GameObject modelGo;
private Animator anim;
public void Init(GameObject modelGo)
{
this.modelGo = modelGo;
this.modelGo.name = "model";
this.modelGo.transform.SetParent(this.transform);
this.anim = this.modelGo.GetComponent<Animator>();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AssetData : MonoBehaviour
{
public int id;
public string path;
public string prefab_name;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AssetManager : MonoBehaviour
{
public static AssetManager instance;
public System.Action<float> onProgress;
private Dictionary<string, GameObject> dicPrefabs = new Dictionary<string, GameObject>();
private void Awake()
{
AssetManager.instance = this;
}
public void LoadAllAssets()
{
List<AssetData> assetData = DataManager.instance.GetAssetDatas();
for (int i = 0; i < assetData.Count; i++)
{
AssetData data = assetData[i];
var fullPath = string.Format("{0}/{1}", data.path, data.prefab_name);
Debug.Log(fullPath);
this.StartCoroutine(this.LoadAsync(fullPath));
}
}
private int loadedAssetCount = 0;
public IEnumerator LoadAsync(string path)
{
var req = Resources.LoadAsync<GameObject>(path);
yield return req;
//어셋로드 완료
var arr = path.Split('/');
var key = arr[arr.Length - 1];
this.dicPrefabs.Add(key, (GameObject)req.asset);
++this.loadedAssetCount;
Debug.LogFormat("{0}/{1}\t{2}", this.loadedAssetCount, DataManager.instance.GetAssetDatas().Count, path);
var per = (float)this.loadedAssetCount / DataManager.instance.GetAssetDatas().Count;
this.onProgress(per);
}
/// <summary>
/// get prefab by prefab_name
/// </summary>
/// <param name="key">prefab name</param>
/// <returns></returns>
public GameObject GetPrefab(string key)
{
return this.dicPrefabs[key];
}
}
- App 오브젝트의 자식 오브젝트로서 존재한다.
- LoadAsync 메서드를 통해 ResouceRequest 타입의 값을 반환하고 해당 값을 비동기로 로드했다면 return 아래 코드를 실행한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterData : MonoBehaviour
{
public int id;
public string name;
public string prefab_name;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
using System.Linq;
public class DataManager
{
public static readonly DataManager instance = new DataManager();
private Dictionary<int, CharacterData> dicCharacterDatas;
private Dictionary<int, AssetData> dicAssetDatas;
private const string CHARACTER_DATA_PATH = "Data/character_data";
private const string ASSET_DATA_PATH = "Data/asset_data";
private DataManager()
{
}
public void LoadAssetData()
{
var asset = Resources.Load<TextAsset>(ASSET_DATA_PATH);
var json = asset.text;
this.dicAssetDatas = JsonConvert.DeserializeObject<AssetData[]>(json).ToDictionary(x => x.id);
}
public void LoadCharacterData()
{
var asset = Resources.Load<TextAsset>(CHARACTER_DATA_PATH);
var json = asset.text;
this.dicCharacterDatas = JsonConvert.DeserializeObject<CharacterData[]>(json).ToDictionary(x => x.id);
}
public List<AssetData> GetAssetDatas()
{
return this.dicAssetDatas.Values.ToList();
}
public List<CharacterData> GetCharacterDatas()
{
return this.dicCharacterDatas.Values.ToList();
}
public CharacterData GetCharacterData(int id)
{
return this.dicCharacterDatas[id];
}
}
- 데이터테이블을 역질렬화하여 링큐를 통해 딕셔너리에 값을 입력하는 기능을 수행하는 메서드들과 해당 딕셔너리의 값들을 리스트, 데이터타입형태로 반환하는 기능을 수행하는 메서드들로 이루어져있다.
[
{
"id": "100",
"path": "Prefabs",
"prefab_name": "DeerStandard"
},
{
"id": "101",
"path": "Prefabs",
"prefab_name": "BadgerStandard"
},
{
"id": "102",
"path": "Prefabs",
"prefab_name": "LionStandard"
},
{
"id": "103",
"path": "Prefabs/UI",
"prefab_name": "UIDeerStandard"
},
{
"id": "104",
"path": "Prefabs/UI",
"prefab_name": "UIBadgerStandard"
},
{
"id": "105",
"path": "Prefabs/UI",
"prefab_name": "UILionStandard"
}
]
[
{
"id": "100",
"name": "Deer",
"prefab_name": "DeerStandard"
},
{
"id": "101",
"name": "Badger",
"prefab_name": "BadgerStandard"
},
{
"id": "102",
"name": "Lion",
"prefab_name": "LionStandard"
}
]