유니티 RPG - 50. 플레이어 스킬구현 - 1
Intro
이번 포스팅에서는 플레이어의 스킬 공격을 구현해보았습니다.
-
SkillDataDTO : SkillData 와 JSON 간의 직렬/역직렬화를 위한 클래스
-
SkillData : DTO 데이터로 스킬데이터를 초기화
- Skill : 스킬의 공통적인 부분을 관리
- 추상클래스로, 개별 스킬들을 Skill 클래스를 상속
- Activate() : 스킬 사용을 위한 함수
- 스킬 사용주체의 Animator 를 캐싱해두어 최적화 고려
- Slash, Buff, IceShot… : 개별 스킬 클래스
- 플레이어가 스킬 사용 가능상태인지 확인후 스킬 실행
- 스킬별 특징을 여기서 구현
- SkillManager : 스킬 시스템을 관리
- 저장된 스킬데이터(JSON)를 불러와 캐싱
- 스킬 사용을 위한 스킬 인스턴스 생성 및 스킬 사용 요청
스킬 시스템을 위한 큰 틀을 잡고 진행하였습니다.
SkillData.json
스킬 데이터를 저장하는 JSON 파일입니다.
스킬의 Id 및 애니메이션 실행을 위한 Id, 데미지등이 작성되어있습니다.
무기별 스킬을 관리할 수 있도록 하였습니다.
{
"Sword": [
{
"id": "slash",
"name": "Slash",
"animId": 10,
"cooldown": 2.0,
"damage": 25
}
],
"Staff": [
{
"id": "iceshot",
"name": "IceShot",
"animId": 20,
"cooldown": 3.0,
"damage": 50
}
],
"Public": [
{
"id": "buff",
"name": "Buff",
"animId": 0,
"cooldown": 5.0,
"damage": 0
}
],
}
SkillDataDTO.cs
작성된 스킬데이터를 직렬/역직렬화를 위한 DTO 클래스입니다.
[System.Serializable]
public class SkillDataDTO
{
public string id;
public string name;
public int animId;
public float cooldown;
public int damage;
}
SkillData.cs
DTO 정보를 가지고 스킬 데이터를 초기화합니다.
데이터에 접근할 수 있는 프로퍼티를 제공합니다.
public class SkillData
{
public string Id { get; private set; }
public string Name { get; private set; }
public int AnimId { get; private set; }
public float Cooldown { get; private set; }
public int Damage { get; private set; }
public SkillData(SkillDataDTO dto)
{
Id = dto.id;
Name = dto.name;
AnimId = dto.animId;
Cooldown = dto.cooldown;
Damage = dto.damage;
}
}
Skill.cs
스킬이 공통적으로 가지는 정보를 관리하는 클래스입니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
Skill
- 스킬이 공통적으로 가지는 데이터 관리
- InitAnimator() : 스킬사용주체의 애니메이터 캐싱
- Activate() : 스킬 사용
- 개별 스킬클래스에서 구현
*/
public abstract class Skill
{
protected SkillData data;
protected Animator anim;
public Skill(SkillData data)
{
this.data = data;
}
// 애니메이션 캐싱
public void InitAnimator(GameObject user)
{
anim = user.GetComponent<Animator>();
}
public abstract void Activate(GameObject user);
}
- 스킬을 사용한 주체의 애니메이션을 설정하기위해 Animator 정보가 필요합니다.
- 스킬을 사용할때마다 GetComponent 하여 접근하는것은 비효율적이라서 캐싱하여 사용합니다.
- 스킬 클래스들은 Skill 클래스를 상속받아 구현합니다.
Slash, Buff, IceShot.cs …
개별 스킬 클래스입니다.
여기에 그 스킬만의 특징을 구현합니다.
저는 스킬특성을 더이상 나누지 않았지만, 구조를 더 나누어 관리가능합니다.
using UnityEngine;
/*
Slash
- 검스킬
*/
public class Slash : Skill
{
public Slash(SkillData data) : base(data) { }
// 스킬 사용
public override void Activate(GameObject user)
{
// 올바른 무기를 장착했는지 여부
bool hasWeapon = WeaponManager.Instance.currentWeapon.type == WeaponType.Sword;
if (anim == null)
{
Debug.Log($"{user} 의 Animator가 존재하지 않음.");
return;
}
else if(!hasWeapon)
{
Debug.Log($"장착한 무기로는 스킬을 사용할 수 없습니다.");
}
else
{
anim.SetTrigger("Skill");
anim.SetInteger("SkillId", data.AnimId);
Debug.Log($"{data.Name} : 사용!");
}
}
}
/*
Buff
- 버프스킬
*/
public class Buff : Skill
{
public Buff(SkillData data) : base(data) { }
public override void Activate(GameObject user)
{
// 무기를 장착했는지 여부
bool hasWeapon = WeaponManager.Instance.currentWeapon != null;
if (anim == null)
{
Debug.Log($"{user} 의 Animator가 존재하지 않음.");
return;
}
else if (!hasWeapon)
{
Debug.Log($"장착한 무기가 없습니다.");
}
else
{
anim.SetTrigger("Skill");
anim.SetInteger("SkillId", data.AnimId);
Debug.Log($"{data.Name} : 사용!");
}
}
}
/*
IceShot
- 마법스킬
*/
public class IceShot : Skill
{
public IceShot(SkillData data) : base(data) { }
public override void Activate(GameObject user)
{
// 올바른 무기를 장착했는지 여부
bool hasWeapon = WeaponManager.Instance.currentWeapon.type == WeaponType.Staff;
if (anim == null)
{
Debug.Log($"{user} 의 Animator가 존재하지 않음.");
return;
}
else if (!hasWeapon)
{
Debug.Log($"장착한 무기로는 스킬을 사용할 수 없습니다.");
}
else
{
anim.SetTrigger("Skill");
anim.SetInteger("SkillId", data.AnimId);
Debug.Log($"{data.Name} : 사용!");
}
}
}
- 스킬은 플레이어가 장착한 무기에따라 사용이 가능해집니다.
Skillmanager.cs
스킬 시스템을 관리하는 매니저입니다.
스킬 데이터를 불러와 캐싱해두고 필요할때마다 검색하여 스킬 인스턴스를 생성한뒤 사용합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using Newtonsoft.Json;
/// <summary>
/// SkillManager
/// 1. 스킬 데이터 로드
/// 2. 스킬 실행
///
/// </summary>
public class SkillManager : Singleton<SkillManager>
{
public List<Skill> playerSkills = new();
private Dictionary<string, SkillData> skillDataDictionary = 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;
}
}
// 스킬 사용(Id 기반)
public void ExecuteSkill(string skillId, GameObject user)
{
var skillData = GetSkillDataById(skillId);
if (skillData == null) return;
Skill skill = CreateSkillInstance(skillData);
skill.Activate(user);
}
}
스킬 사용
스킬포인트, 동적으로 Key에 스킬을 바인딩하는 시스템은 아직 구현되지 않았습니다.
스킬슬롯 GUI를 만들고, 각 슬롯에 스킬을 고정적으로 매핑하였습니다.
각 슬롯은 설정한 Id를 통해 초기화하며, 플레이어 키입력이 들어오면 스킬을 사용합니다.
/*
SkillSlotUI
- 플레이어 스킬슬롯의 스킬설정 및 스킬사용
*/
public class SkillSlotUI : MonoBehaviour
{
[SerializeField] string skillId; // 슬롯의 스킬 ID
private Skill skill;
private void Start()
{
Init();
}
private void Init()
{
SkillData skillData = SkillManager.Instance.GetSkillDataById(skillId);
skill = SkillManager.Instance.CreateSkillInstance(skillData);
skill.InitAnimator(GameManager.Instance.player.gameObject);
}
public void UseSkill()
{
if (skill != null)
skill.Activate(GameManager.Instance.player.gameObject);
}
}
스킬키 입력은 PlayerController 에서 진행됩니다.
A,S,D 키 입력을 통해 각 스킬슬롯에 스킬사용을 요청합니다.
[SerializeField] private SkillSlotUI[] skillSlots; // 스킬 슬롯
// 스킬사용
private void DoSkill()
{
if (isAttacking || isDead || isCutscenePlaying)
return;
if (Input.GetKeyDown(KeyCode.A))
{
skillSlots[0].UseSkill();
}
if (Input.GetKeyDown(KeyCode.S))
{
skillSlots[1].UseSkill();
}
if (Input.GetKeyDown(KeyCode.D))
{
skillSlots[2].UseSkill();
}
}
댓글남기기