
ScriptableObject로 스탯 구현하기
스크립터블 오브젝트를 통해 캐릭터의 다양한 능력치와 그 기본값을
유니티 에디터에서 쉽게 관리할 수 있도록 구조화한 것
<StatData.cs>
using System.Collections.Generic;
using UnityEngine;
// 캐릭터의 능력치 종류를 정의하는 열거형
public enum StatType
{
Health, // 체력
Speed, // 이동 속도
ProjectileCount, // 투사체 수 (예: 멀티샷)
}
// 캐릭터의 스탯 데이터를 저장하는 ScriptableObject
[CreateAssetMenu(fileName = "New StatData", menuName = "Stats/Chracter Stats")]
public class StatData : ScriptableObject
{
public string characterName;
public List<StatEntry> stats;
}
// 하나의 스탯 정보를 정의하는 클래스
[System.Serializable]
public class StatEntry
{
public StatType statType; // 어떤 종류의 능력치인지
public float baseValue; // 해당 능력치의 기본 값
}
StatHandler가 StatData로부터 캐릭터의 기본 능력치를 로드
게임 중 능력치를 조회하거나 일시적/영구적으로 변경하는 기능
public class StatHandler : MonoBehaviour
{
public StatData statData; // 이 캐릭터가 사용할 능력치 데이터 (ScriptableObject)
// 현재 적용 중인 능력치 값들을 저장하는 딕셔너리
private Dictionary<StatType, float> currentStats = new Dictionary<StatType, float>();
private void Awake()
{
InitializeStats(); / 게임 시작 시 스탯 초기화
}
// StatData에 정의된 기본 스탯을 딕셔너리에 복사하여 저장
private void InitializeStats()
{
foreach (StatEntry entry in statData.stats)
{
currentStats[entry.statType] = entry.baseValue;
}
}
// 특정 스탯의 현재 값을 가져옴 (없으면 0 반환)
public float GetStat(StatType statType)
{
return currentStats.ContainsKey(statType) ? currentStats[statType] : 0;
}
// 스탯을 변경하는 함수
// - amount: 변경할 값 (양수 또는 음수)
// - isPermanent: true면 영구 적용, false면 일정 시간 후 원래대로 복구됨
// - duration: 임시 적용일 경우 유지 시간 (초)
public void ModifyStat(StatType statType, float amount, bool isPermanent = true, float duration = 0)
{
if (!currentStats.ContainsKey(statType)) return;
currentStats[statType] += amount;
if (!isPermanent)
{
// 일정 시간이 지나면 스탯을 원래대로 되돌림
StartCoroutine(RemoveStatAfterDuration(statType, amount, duration));
}
}
// 일정 시간이 지난 후 스탯을 원래 값으로 되돌리는 코루틴
private IEnumerator RemoveStatAfterDuration(StatType statType, float amount, float duration)
{
yield return new WaitForSeconds(duration);
currentStats[statType] -= amount;
}
}
캐릭터의 최대 체력을 StatHandler에서 가져온 Health 스탯 값으로 설정
게임 시작 시 현재 체력도 이 최대 체력으로 초기화
<ResourceController.cs>
// StatHandler에서 현재 체력 스탯 값을 읽음
public float MaxHealth => statHandler.GetStat(StatType.Health);
private void Start()
{
CurrentHealth = statHandler.GetStat(StatType.Health);
}
입력된 이동 방향에 StatHandler에서 가져온 속도 스탯을 적용
만약 넉백 중이라면 속도를 줄인 뒤 넉백 힘을 더해
최종적으로 캐릭터의 이동 속도를 결정하고 애니메이션을 업데이트
<BaseController .cs>
private void Movement(Vector2 direction)
{
// StatHandler에서 현재 속도 스탯 값을 읽음
direction = direction * statHandler.GetStat(StatType.Speed);
if (knockbackDuration > 0.0f)
{
direction *= 0.2f;
direction += knockback;
}
_rigidbody.velocity = direction;
animationHandler.Move(direction);
}
공격 시 StatHandler에서 가져온 '투사체 수' 스탯을 기본 발사 개수에 더해 실제 발사될 투사체 총 개수를 계산
각 투사체가 정해진 각도 간격과 무작위 편차를 적용하여 퍼지게 발사
<RangeWeaponHandler .cs>
private StatHandler statHandler;
protected override void Start()
{
base.Start();
projectileManager = ProjectileManager.Instance;
statHandler = GetComponentInParent<StatHandler>();
}
public override void Attack()
{
base.Attack();
float projectileAngleSpace = multipleProjectileAngle;
// StatHandler에서 현재 투사체 수 스탯 값을 읽음
int numberOfProjectilePerShot = numberofProjectilesPerShot + (int)statHandler.GetStat(StatType.ProjectileCount); ;
float minAlge = -(numberOfProjectilePerShot / 2f) * projectileAngleSpace;
for(int i = 0; i < numberOfProjectilePerShot; i++)
{
float angle = minAlge + projectileAngleSpace * i;
float randomSpread = Random.Range(-spread, spread);
angle += randomSpread;
CreateProjectile(Controller.LookDirection, angle);
}
}
프로젝트 씬에서 마우스 우클릭하면 이런 식으로 뜬다.
Character Stats를 누르면 사진의 Data폴더 아래에 있는 것들처럼 하나 생성된다.

Character Name에는 해당 오브젝트의 레이어 이름을 적어준다.
이후 Stats의 변수를 지정하고 값도 지정하면 끝
몬스터도 마찬가지로 똑같은 방법으로 만들고
Character Name을 Enemy로 하면 몬스터에게 적용시킬 스텟을 구현할 수 있는 것

마지막에 꼭 해당 오브젝트의 Stat Handler스크립트에 할당시키자.