[Unity] Project. DayLight_3

Lingtea_luv·2025년 6월 21일

Project

목록 보기
29/38
post-thumbnail

Project. DayLight


주말임에도 모두 함께 작업을 했다. 감사함니다!!

작업 완료 기능

  1. 플레이어 이동 로직 - 계단 오르기, 달리기, 웅크리기
  2. 플레이어 상호작용 - 문 열기, 루팅, 공격(총)
  3. 플레이어 인벤토리 기능 전반
  4. 플레이어 파라미터 - 스탯 적용 알고리즘, 속성 연동

작업 중 기능

  1. 플레이어 이동 로직 - 파쿠르
  2. 플레이어 상호작용 - 공격(근접 무기), 피격
  3. 플레이어 모션 - 애니메이터 작업 + 상태 패턴
  4. 인벤토리 UI, 퀵슬롯 기능 및 UI
  5. 몬스터 AI - Patrol, 플레이어 추적

공유 코드

오늘 작업한 내역은 문 열기 기능, 플레이어가 가지고 있는 속성들의 적용 알고리즘이다. 체력, 허기, 갈증, 스태미나의 수치에 따라 적용되는 상태가 달라서 이를 유기적으로 연결되도록 구현하는 것이 생각보다 쉽지 않았다. 그래서 해당 코드를 공유하고자한다.

Parameter

Property<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 
}

Hp

Parameter를 상속받는 스탯 중 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);
    }
}
profile
뚠뚠뚠뚠

0개의 댓글