내일배움캠프 44일차 TIL <Unity ScriptableObject> 06/11

정광훈(Unity_9기)·2025년 6월 11일

TIL (Today I Learned)

목록 보기
54/97
post-thumbnail

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스크립트에 할당시키자.

0개의 댓글