본문 바로가기
게임 프로그래밍/유니티 활용

[유니티 활용] ScriptableObject 와 Reflection 을 이용한 간단한 버프 시스템 만들기

by 레오란다 2023. 1. 3.
반응형

안녕하세요. 이번 글에서는 간단한 버프 시스템을 만들어 보는 방법에 대해서 알아보도록 하겠습니다. 이번 글은 이전에 작성한 아래의 글을 베이스로 작성하도록 하겠습니다. 이 글에서 작성하는 에셋 정보나 UI 설정 등은 아래 글을 참고해 주세요.

 

 

 

[유니티 활용] ScriptableObject 를 이용한 스킬 시스템 만들기

이번 글에서는 ScriptableObject를 활용해 캐릭터의 스킬 시스템을 구현해 보도록 하겠습니다. 구현 순서는 다음과 같습니다. 1. 무료 에셋 다운로드 2. Player 캐릭터 3. ScriptableObject 로 스킬 생성 4. 스

ugames.tistory.com

 

 

▶ 기본 개념

ScriptableObject 와 Reflection 을 이용하는 버프 시스템의 기본 개념은 다음과 같습니다.

  • ScriptableObject 로 버프 정보 생성 (스탯 증가량, 지속 시간 등)
  • Reflection 의 문자열로 객체의 변수를 제어하는 방법을 활용

 

▶ ScriptableObject 로 버프 정의하기

  • Project 창에서 [Assets > Scripts] 에서 마우스 우클릭 하고 [Create > C# Script] 로 새 스크립트를 생성하고 이름을 SOBuff 로 변경합니다.
  • SOBuff 의 코드를 다음과 같이 작성합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu]
public class SOBuff : ScriptableObject
{
   // 버프 지속시간
   public float durTime;
   // 증가 값
   public int value;
   // 스탯 이름
   public string statName;   
}
  • 다시 마우스 우클릭 [Create > SO Buff] 로 ScriptableObject 에셋을 만들고 이름을 StrBuff 로 변경 후 아래와 같이 설정합니다. 

ScriptableObject 에셋 설정-StrBuff
ScriptableObject - StrBuff

* StatName 은 Player 스크립트에 정의된 스탯 변수의 이름과 동일해야 합니다.

 

  • StrBuff 를 선택하고 CTRL + D 로 복사한 다음 이름을 SpdBuff 로 변경하고 아래와 같이 설정합니다.

ScriptableObject 에셋 설정- SpdBuff
ScriptableObject - SpdBuff

* StatName 은 Player 스크립트에 정의된 스탯 변수의 이름과 동일해야 합니다.

 

 

▶ Buff 객체 만들기

버프는 보통 지속 시간이 있기 때문에 이를 위해 코루틴을 사용할 것입니다. Buff 객체는 ScriptableObject 로 만들어진 버프 에셋 중 어떤 걸 사용하는지에 따라 버프의 종류가 결정됩니다.

  • Project 창에서 Assets 밑에 Resources 폴더를 생성합니다.
  • Resources 폴더 밑에 Buffs 폴더를 생성합니다.
  • Buffs 폴더에서 마우스 우클릭 [Create > Prefab] 을 선택하고 이름을 StrBuff 로 변경합니다.
  • StrBuff 에 Buff 라는 이름의 새 스크립트를 추가하고 다음과 같이 코드를 작성합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;

public class Buff : MonoBehaviour
{
   // ScriptableObject 로 만든 버프
   public SOBuff buff;   

   // 버프 적용
   // obj 는 버프가 적용될 객체
   // cbDone 은 버프가 끝났음을 obj 에게 알리는 용도
   public void Activated(object obj, System.Action cbDone)
   {
      StartCoroutine(SC_Timer(obj, cbDone));     
   }

   protected IEnumerator SC_Timer(object obj, System.Action cbDone)
   {
      // Reflection 으로 obj에 선언된 필드(변수) 를 가져온다
      var fi = obj.GetType().GetTypeInfo().GetDeclaredField(buff.statName);

      // 현재 값
      int v = (int)fi.GetValue(obj);

      // 버프가 적용된 값
      int buffed = v + buff.value;

      // obj에 버프된 스탯 적용
      fi.SetValue(obj, buffed);

      // 버프 시간 동안 대기
      float elapsed = 0;

      while (elapsed <= buff.durTime)
      {
         elapsed += Time.deltaTime;
         yield return null;
      }

      // obj 에 값을 원래대로 되돌림
      fi.SetValue(obj, v);

      // obj 에 버프가 끝났음을 알림
      cbDone.Invoke();
   }
}
  • StrBuff 를 선택하고 Inspector 창에서 다음과 같이 ScriptableObject 인 StrBuff 를 연결합니다.

Buff 객체 설정
Buff 설정

  • [Assets > Resources > Buffs] 에 있는 StrBuff 를 선택하고 CTRL + D 를 눌러 복사한다음 이름을 SpdBuff 로 변경합니다. 그리고 위와 동일한 방법으로 ScriptableObject 인 SpdBuff 를 연결해 줍니다.

 

▶ Player 스크립트

Player 스크립트는 위에 링크한 이전 글에서 작성한 스크립트에 추가적으로 작성하도록 하겠습니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
   Animator anim;

   List<Buff> buffs = new List<Buff>();
   
   // str 스탯
   public int str;

   // spd 스탯
   public int spd;

   private void Start()
   {
      anim = GetComponent<Animator>();
   }

   public void ActivateSkill(SOSkill skill)
   {
      anim.Play(skill.animationName);
      print(string.Format("적에게 스킬 {0} 로 {1} 의 피해를 주었습니다.", skill.name, skill.damage));
   }

   // 버프 추가
   public void AddBuff(Buff buff)
   {
      // 버프 목록에 이미 있으면 무시
      if (buffs.Contains(buff) == false)      
      {
         // 없으면 버프 목록에 추가
         buffs.Add(buff);         

         // 버프 적용
         buff.Activated(this, () =>
         {
            // 버프 시간이 끝나면 이 부분의 코드가 진행됨
            buffs.Remove(buff);
            Destroy(buff.gameObject);
            print(buff.buff.name + ": 버프 제거됨");
            PrintStats();
         });
         
         print(buff.buff.name + ": 버프 적용");
         PrintStats();
      }
   }

   // Console 창에 스탯 출력
   void PrintStats()
   {
      print("str: " + str.ToString());
      print("spd: " + spd.ToString());
   }
}

 

▶ Test UI 만들기

이제 버프를 적용시키기 위한 테스트용 UI 를 작성하도록 하겠습니다. 테스트 UI 는 버튼을 만들고 클릭하면 Player 스크립트에 프리팹으로 생성한 버프 객체를 넣어주는 역할입니다. 

 

  • 버튼 UI 를 한 개 생성하고 좌측 하단에 배치해 줍니다. 이름은 StrBuffButton 으로 합니다.
  • 버튼 객체에 TestBuff 라는 이름의 새 스크립트를 추가하고 다음과 같이 작성합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestBuff : MonoBehaviour
{
   // 버프 프리팹
   public Transform prefabBuff;

   // Player 스크립트
   public Player player;

   // 버튼 클릭시 호출
   public void OnClicked()
   {
      // 프리팹으로 객체 생성
      var buff = Instantiate(prefabBuff);

      // 생성된 버프 객체를 player에 추가
      player.AddBuff(buff.GetComponent<Buff>());
   }
}
  • 아래와 같이 Inspector 창에서 TestBuff 와 버튼을 설정합니다.

TestBuff 설정
TestBuff

  • StrBuffButton 을 선택하고 CTRL+D 로 복사한 후 SpdBuffButton 으로 이름을 변경한 다음 위와 같은 방법으로  PrefabBuff 필드에 SpdBuff 프리팹을 연결해 줍니다.

위와 같이 구현하고 Play 한 다음 StrBuffButton 을 클릭하면 Console 창에 다음과 같이 str이 버프 된 Stat 이 출력되고 5 초후 다시 원상태로 돌아옵니다. SpdBuffButton 을 클릭하면 spd 가 버프됩니다.

 

결과
결과

 

이 코드를 기반으로 조금만 응용하면 중첩이 가능한 버프도 구현할 수 있습니다. 또 버프가 적용될 때 (코드 상에서는 Console 창에 "버프 적용" 이라고 출력하는 부분) 캐릭터 Animation 을 추가하거나 파티클을 추가하여 버프가 적용되는 효과를 줄 수도 있습니다.

 

 

반응형

댓글