
Project. DayLight주말임에도 모두 함께 작업을 했다. 감사함니다!!
오늘 작업한 내역은 문 열기 기능, 플레이어가 가지고 있는 속성들의 적용 알고리즘이다. 체력, 허기, 갈증, 스태미나의 수치에 따라 적용되는 상태가 달라서 이를 유기적으로 연결되도록 구현하는 것이 생각보다 쉽지 않았다. 그래서 해당 코드를 공유하고자한다.
ParameterProperty<T>와 비슷하지만, State에 따라 실행되는 메서드가 다르다는 추가 성질을 가지고 있다.
public class Parameter
{
private float _value;
protected float Value
{
get => _value;
set
{
// 변경된 값이 기존의 값과 일치하지 않는 경우에만
if(!EqualityComparer<float>.Default.Equals(_value, value))
{
_value = value;
StateUpdate();
OnChanged?.Invoke(_value);
}
}
}
public event Action<float> OnChanged;
// parameter의 상태 enum 관리
public ParamState State { get; private set; }
// 초기화 메서드, 동시에 상태 동기화
public void Init(float value)
{
_value = value;
StateUpdate();
}
// 값에 따른 상태 업데이트 메서드
private void StateUpdate()
{
if (_value > 99)
{
State = ParamState.Full;
}
else if (_value > 40)
{
State = ParamState.Basic;
}
else if (_value > 10)
{
State = ParamState.Lack;
}
else
{
State = ParamState.Depletion;
}
}
// 상태에 따라 호출되는 메서드 virtual로 선언(호환되는 매개변수에 따라 override할 메서드 선택)
public virtual void Act() { }
public virtual void Act(ref float stat, float baseValue, float offset) { }
// 스탯 회복 메서드
public void Recover(float value)
{
Value = Value + value > 100 ? 100 : Value + value;
}
// 스탯 감소 메서드
public void Decrease(float value)
{
Value = Value - value < 0 ? 0 : Value - value;
}
// 상태에 따라 호출되는 메서드 virtual로 선언(호환되는 매개변수에 따라 override할 메서드 선택)
public virtual void Penalty(ref float stat, float baseValue, float offset) { }
public virtual void Penalty() { }
public virtual void ResetValue(ref float stat, float baseValue, float offset) { }
public virtual void ResetValue() { }
public virtual void Advantage(ref float stat, float baseValue, float offset) { }
public virtual void Advantage() { }
}
// parameter enum
public enum ParamState
{
Full, Basic, Lack, Depletion
}
HpParameter를 상속받는 스탯 중 Hp를 예시로 가져왔다. 생성자를 가지며, 생성자에서 Init을 호출하여 State를 동기화시킨다.
public class Hp : Parameter
{
// virtual로 선언된 메서드 override
public override void Act(ref float atk, float baseValue, float offset)
{
// 상태에 따라 차별되는 동작 수행
switch (State)
{
case ParamState.Full:
Advantage(ref atk, baseValue, offset);
break;
case ParamState.Basic:
ResetValue(ref atk, baseValue, offset);
break;
case ParamState.Lack:
break;
case ParamState.Depletion:
Penalty();
break;
}
}
// 충족 상태일 때 적용되는 스탯 보너스
public override void Advantage(ref float atk, float baseValue, float offset)
{
atk = baseValue + offset;
}
// 보통 상태일 때 적용되는 스탯
public override void ResetValue(ref float atk, float baseValue, float offset)
{
atk = baseValue;
}
// 고갈 상태일 때 적용되는 패널티
public override void Penalty()
{
GameManager.Instance.GameOver();
}
// 생성자 -> Init 호출
public Hp(float value)
{
Init(value);
}
}
PlayerProperty전체 스크립트 중에 Hp와 연관된 코드만 정리해서 작성했다. 구현하고자 한 기능은 다음과 같다.
1. 나머지 스탯이 전부 결핍 상태가 아닌 경우 체력 회복
2. 나머지 스탯 중 하나라도 고갈 상태일 경우 체력 감소
3. 체력 상태에 따른 공격 데미지 연동
public class PlayerProperty : MonoBehaviour
{
// 기본 공격 데미지, 데미지 보정 수치 -> 인스펙터 상에서 조절
[SerializeField] private float _baseAtkDamage;
[SerializeField] private float _atkDamageOffset;
// player가 가지고 있는 parameter 스탯
private Hp _hp;
private Hunger _hunger;
private Thirsty _thirsty;
private Stamina _stamina;
// 공격 데미지 프로퍼티
public Property<float> AtkDamage;
// 임시로 공격 데미지를 캐싱하기 위한 변수
private float _atkDamage;
// 코루틴에서 사용할 캐싱 변수
private WaitForSeconds _delay;
// 코루틴 중복 생성 방지 플래그
private bool _isOnCorRecoverHp;
private bool _isOnCorDecreaseHp;
// 결핍 상태, 고갈 상태 확인 변수
private bool _isOnLack;
private bool _isOnDepletion;
// 내부 필드 초기화 Awake에서 처리
private void Awake()
{
Init();
}
private void Update()
{
FieldUpdate();
ParameterUpdate();
ParameterAct();
}
// 상태 변수 업데이트 메서드
private void FieldUpdate()
{
_isOnLack = (_hunger.State == ParamState.Lack) ||
(_thirsty.State == ParamState.Lack) ||
(_stamina.State == ParamState.Lack);
_isOnDepletion = (_hunger.State == ParamState.Depletion) ||
(_thirsty.State == ParamState.Depletion) ||
(_stamina.State == ParamState.Depletion);
}
// 상태에 따른 회복, 감소 로직 변경 메서드
private void ParameterUpdate()
{
if (!_isOnLack && !_isOnCorRecoverHp)
{
StartCoroutine(RecoverHp());
}
if (_isOnDepletion && !_isOnCorDecreaseHp)
{
StartCoroutine(DecreaseHp());
}
}
// Hp값에 따른 공격 데미지 업데이트 메서드
private void ParameterAct()
{
_hp.Act(ref _atkDamage, _baseAtkDamage, _atkDamageOffset);
AtkDamage.Value = _atkDamage;
}
// 체력 회복 코루틴
private IEnumerator RecoverHp()
{
_isOnCorRecoverHp = true;
while (true)
{
if (_isOnLack) break;
_hp.Recover(1f);
yield return _delay;
}
_isOnCorRecoverHp = false;
}
// 체력 감소 코루틴
private IEnumerator DecreaseHp()
{
_isOnCorDecreaseHp = true;
while (true)
{
if (!_isOnDepletion) break;
_hp.Decrease(1f);
yield return _delay;
}
_isOnCorDecreaseHp = false;
}
// 내부 필드 초기화 메서드
private void Init()
{
_hp = new HP(100);
_hunger = new Hunger(100);
_thirsty = new Thirsty(100);
_stamina = new Stamina(100);
_delay = new WaitForSeconds(1f);
AtkDamage = new Property<float>(_baseAtkDamage);
}
}