유니티 RPG - 52. 플레이어 스킬구현 - 2
Intro
이번 포스팅에서는 스킬을 사용할때 이펙트를 표시하는것을 구현하였습니다.
스킬 이펙트는 개별 스킬클래스에서 처리하도록 하였습니다.
또한 스킬은 많이 사용되므로 풀링을 통해 스킬 및 스킬이펙트를 재사용할 수 있도록 하였습니다.
SkillManager.cs
스킬 매니저의 변경점으로 풀링을 적용하였고,
스킬에 맞는 스킬 이펙트를 로딩하여 적용할 수 있도록 하였습니다.
/// <summary>
/// SkillManager
/// 1. 스킬 데이터 로드
/// 2. 스킬 실행
/// 3. 스킬 오브젝트 풀링
/// </summary>
public class SkillManager : Singleton<SkillManager>
{
public List<Skill> playerSkills = new();
private Dictionary<string, SkillData> skillDataDictionary = new(); // 스킬 데이터 캐싱 딕셔너리
private Dictionary<string, Queue<Skill>> skillPool = new(); // 스킬 풀링 딕셔너리
protected override void Awake()
{
base.Awake();
LoadSkillData();
}
// 스킬 데이터 불러오기(캐싱)
private void LoadSkillData()
{
string path = Path.Combine(Application.persistentDataPath, "SkillData.json");
if(File.Exists(path))
{
string jsonData = File.ReadAllText(path);
var skillDict = JsonConvert.DeserializeObject<Dictionary<string, List<SkillDataDTO>>>(jsonData);
if(skillDict != null)
{
foreach(var category in skillDict)
{
foreach(var skillDTO in category.Value)
{
SkillData skillData = new SkillData(skillDTO);
skillDataDictionary[skillData.Id] = skillData;
}
}
}
else
{
Debug.LogWarning("Json 데이터를 파싱할 수 없음.");
}
}
else
{
Debug.LogWarning("SkillData.json 파일이 없음.");
}
}
// 스킬 인스턴스 생성
public Skill CreateSkillInstance(SkillData data)
{
switch (data.Id)
{
case "buff":
return new Buff(data);
case "iceshot":
return new IceShot(data);
case "slash":
return new Slash(data);
default:
Debug.Log($"정의되지 않은 스킬 ID : {data.Id}");
return null;
}
}
// ID로 스킬 데이터 가져오기
public SkillData GetSkillDataById(string id)
{
if(skillDataDictionary != null && skillDataDictionary.TryGetValue(id, out var resultData))
{
return resultData;
}
else
{
Debug.LogWarning("ID에 해당하는 스킬데이터가 없음.");
return null;
}
}
// 풀에서 스킬 가져오기
private Skill GetSkillFromPool(SkillData data)
{
// 풀에 스킬이 있으면 반환
if(skillPool.TryGetValue(data.Id, out var queue) && queue.Count > 0)
{
return queue.Dequeue();
}
// 없으면 스킬 인스턴스 생성후 반환
return CreateSkillInstance(data);
}
// 스킬 사용 후 풀에넣기
private void ReturnSkillToPool(string skillId, Skill skill)
{
// 새로운 스킬이면
if(!skillPool.ContainsKey(skillId))
{
skillPool[skillId] = new Queue<Skill>();
}
skillPool[skillId].Enqueue(skill);
}
// 스킬 실행
public void ExecuteSkill(string skillId, GameObject user, Action<bool> onSkillExecuted)
{
var skillData = GetSkillDataById(skillId);
if (skillData == null)
{
onSkillExecuted?.Invoke(false);
return;
}
// 스킬 인스턴스 생성
Skill skill = GetSkillFromPool(skillData);
// 이펙트를 미리 로딩 하여
ResourceManager.Instance.LoadEffectPrefab(skillData.EffectName, prefab =>
{
// 스킬 세팅(애니메이터, 이펙트)후 발동
if (prefab != null)
{
skill.SetEffect(prefab); // 이펙트 설정
skill.InitAnimator(user.gameObject); // 애니메이터 캐싱
bool successed = skill.Activate(user); // 스킬 실행 성공여부
if (successed)
{
StartCoroutine(ReturnSkillAfterUse(skillId, skill, skillData.Cooldown));
}
onSkillExecuted?.Invoke(successed); // 성공여부 콜백
}
else
{
Debug.Log($"Failed to load prefab for item : {prefab}");
onSkillExecuted?.Invoke(false); // 실패 콜백
}
});
}
// 스킬 사용 후
private IEnumerator ReturnSkillAfterUse(string skillId, Skill skill, float delay)
{
yield return new WaitForSeconds(delay);
ReturnSkillToPool(skillId, skill); // 풀에 넣어놓기
if (!(skill is IBuffSkill))
skill.cachedEffect.SetActive(false);
}
}
-
스킬 사용 요청을 받으면, 받은 스킬Id를 통해 스킬데이터를 불러오고 Pool에 스킬이 있으면 가져오고 아니면 새로운 스킬 인스턴스를 생성합니다.
- 그런다음, 스킬을 실행하기전 필요한 초기화작업을 실행합니다.
- 애니메이터 및 이펙트 설정(캐싱)
-
스킬 사용의 성공여부를 콜백으로 반환하여 스킬슬롯에서 쿨타임을 적용할지를 결정합니다.
- 스킬을 사용하고난뒤에는 스킬을 풀에 넣고, 이펙트는 비활성화해둡니다.
Skill.cs
스킬 클래스에서는 이펙트를 캐싱해놓을 수 있도록 하였습니다.
/*
Skill
- 스킬이 공통적으로 가지는 데이터 관리
- InitAnimator() : 스킬사용주체의 애니메이터 캐싱
- SetEffect : 이펙트 프리팹 캐싱
- Activate() : 스킬 사용
- 개별 스킬클래스에서 구현
*/
public abstract class Skill
{
protected SkillData data;
protected Animator anim;
protected GameObject effectPrefab;
public GameObject cachedEffect; // 캐싱된 이펙트 오브젝트
public Skill(SkillData data)
{
this.data = data;
}
// 애니메이션 캐싱
public void InitAnimator(GameObject user)
{
anim = user.GetComponent<Animator>();
}
// 이펙트 프리팹 저장
public void SetEffect(GameObject effect)
{
effectPrefab = effect;
}
// 스킬 사용
public abstract bool Activate(GameObject user);
}
Slash, IceShot, Buff…
개별 스킬들은 이펙트를 적절한 위치에 생성하고 캐싱해둡니다.
캐싱된 이펙트가 있다면 활성화시켜 이펙트가 보이도록합니다.
public class Slash : Skill
{
public Slash(SkillData data) : base(data) { }
// 스킬 사용
public override bool Activate(GameObject user)
{
// 올바른 무기를 장착했는지 여부
bool hasWeapon = WeaponManager.Instance.currentWeapon.type == WeaponType.Sword;
if (anim == null)
{
Debug.Log($"{user} 의 Animator가 존재하지 않음.");
return false;
}
else if(!hasWeapon)
{
Debug.Log($"장착한 무기로는 스킬을 사용할 수 없습니다.");
return false;
}
else
{
anim.SetTrigger("Skill");
anim.SetInteger("SkillId", data.AnimId);
if(cachedEffect == null)
{
// 생성된 이펙트가 없으면 생성
cachedEffect = UnityEngine.Object.Instantiate(effectPrefab,
user.transform.position + new Vector3(0,1,0),
user.transform.rotation, SkillManager.Instance.gameObject.transform);
}
else
{
// 생성된 이펙트가 있으면 새로운 위치 지정
cachedEffect.transform.position = user.transform.position + new Vector3(0, 1, 0);
cachedEffect.transform.rotation = user.transform.rotation;
}
cachedEffect.SetActive(true);
return true;
}
}
}
SkillSlotUI.cs
스킬을 사용하는 함수 UseSkill() 함수를 더이상 스킬에 직접 접근하지않고
SkillManager 를 통하여 스킬을 사용할 수 있도록 하였습니다.
// 스킬 사용
public void UseSkill()
{
// 쿨타임 아닐때
if(!isCooldown)
{
// 스킬사용 성공여부에따라 쿨타임적용
SkillManager.Instance.ExecuteSkill(skillId, GameManager.Instance.player.gameObject, successed =>
{
if (successed)
StartCoroutine(Cooldown());
});
}
}
댓글남기기