본문 바로가기

TIL (since 2023.08.07 ~ )

2023-11-23 TIL(Unity 최종 프로젝트 24일차)

 

오늘은 어제자 TIL 작성을 하지 못했기 때문에 어제 했던 내용과 오늘 구현한 내용을 합쳐서 작성해야겠다.

어제는 플레이어의 특수능력 중 하나인 실드를 걸어주는 버프를 작성하였고, 오늘은 플레이어에게 대미지 수치만 적용되어 있었는데, 추후에 장비나 스킬훈련 시스템 등을 위한 플레이어의 스탯이 존재해야 하기 때문에 플레이어 공격력과 방어력을 Scriptable Object로 구현했고 플레이어 사망에 대한 로직과 보스에게 여러 가지 다양한 상태이상에 걸릴 때 플레이어에게도 애니메이션이 출력되어야 하기 때문에 해당 상태머신도 확장시켜 놓았다.

 

먼저 플레이어의 특수능력인 실드는 Buff 시스템과 Playerconditions에서 구현을 하였다.

 

Buff 수정

public enum BuffTypes
{
    speed,
    nostamina,
    shield
}

public class Buff : MonoBehaviour
{
    public float duration;
    public float buffStartTime;
    private PlayerStateMachine playerStateMachine;
    private Playerconditions playerconditions;
    public BuffTypes buffType;
   
    
    public Buff(BuffTypes type, PlayerStateMachine stateMachine)   // 생성자
    {
        buffType = type;
        playerStateMachine = stateMachine;
        playerconditions = playerStateMachine.Player.Playerconditions;
    }


    public void ApplyBuff(float powerUpDuration)
    {
        duration = powerUpDuration;
        buffStartTime = Time.time;

        switch (buffType)
        {
            case BuffTypes.speed:
                playerStateMachine.MovementSpeedModifier = 2.0f;
                break;
            case BuffTypes.nostamina:
                Debug.Log("스태미나 버프!"); 
                playerconditions.ActiveNoStaminaBuff();
                break;
            case BuffTypes.shield:
                Debug.Log("보호막 활성화");
                playerconditions.ActivateShield(1000);
                break;
            default:
                break;
        }
    }

    public void EndBuff()
    {
        switch (buffType)
        {
            case BuffTypes.speed:
                playerStateMachine.MovementSpeedModifier = 1.0f;
                break;
            case BuffTypes.nostamina:
                Debug.Log("스태미너 버프 빠짐");
                playerconditions.DeActivateNoStaminaBuff();
                break;
            case BuffTypes.shield:
                Debug.Log("보호막 사라짐");
                playerconditions.DeActivateShield();
                break;
            default:
                break;
        }
    }
}

 

 

Playerconditions 수정

public class Condition
{
    [HideInInspector]
    // 생략

    public bool isShieldActive;
    public float shieldValue;

   // 생략

    public void ActivateShield(float shieldAmount)
    {
        shieldValue = shieldAmount;
        isShieldActive = true;
    }

    public void DeActivateShield()
    {
        isShieldActive = false;
    }
}


public class Playerconditions : MonoBehaviour
{
    
    void Update()
    {
        // 생략
        if (health.isShieldActive)
        {
            health.Subtract(health.decayRate * Time.deltaTime);
            if(health.curValue == 0.0f)
                health.DeActivateShield();
        }

        // 생략
    }

   
    public void ActivateShield(float shieldAmount)
    {
        health.ActivateShield(shieldAmount);
    }

    public void DeActivateShield()
    {
        health.DeActivateShield();
    }

    
    }
}

 

 

 

그리고 플레이어에게 스탯을 구현하는 로직도 PlayerConditions에 구현을 할까 했는데 아무래도 수치는 다른 팀원분들과 함께 논의하면서 조정해야 하기 때문에 데이터 수정을 자유롭게 하기 위해서 PlayerSO에 데이터 형식으로 추가하였다.

 

 

PlayerSO 수정

public class PlayerSO : ScriptableObject
{
    [field: SerializeField] public PlayerGroundData GroundedData { get; private set; }
    [field: SerializeField] public PlayerAirData AirData { get; private set; }
    [field: SerializeField] public PlayerAttackData AttackData { get; private set; }
    [field: SerializeField] public PlayerStatData StatData { get; private set; }
}

 

 

 

PlayerStatData

[Serializable]

public class PlayerStatData
{
    [field: SerializeField][field: Range(0f, 100f)] public int Power { get; private set; } = 10;  // 플레이어 공격력
    [field: SerializeField][field: Range(0f, 100f)] public int Guard { get; private set; } = 10; // 플레이어 방어력
}

 

그리고 플레이어가 공격 시 주던 데미지는 공격에 적용되는 damage 값만을 가지고 피해를 주었기 때문에 장비를 장착하면 강해지는 플레이어의 특성을 고려하여 공격 시 데미지를 입히는 로직에 공격력만큼 추가하도록 수정하였다.

 

 

PlayerComboAttackState 수정

public class PlayerComboAttackState : PlayerAttackState
{
    // 생략

    AttackInfoData attackInfoData;
    PlayerStatData statData;


public void ApplyAttack(Collider other)
    {
        // 생략

        target?.TakeDamage(attackInfoData.Damage + statData.Power);
        target?.TakeEffect(attackInfoData.AttackEffectType, attackInfoData.AttackEffectValue, stateMachine.Player.gameObject);
    }

 

 

 

다음으로는 플레이어 죽음 처리를 위해서 따로 스테이트를 만들고, 상태머신을 확장하는 김에 피격 시 진입할 상태들도 함께 확장시켰다.

 

PlayerAirBorneState

 

PlayerKnockBackState

 

PlayerDeadState

 

PlayerStateMachine 수정

public class PlayerStateMachine : StateMachine, IDamageable
{
    public Player Player { get; }
    
    // 생략
    
    public PlayerDeadState DeadState { get; }
    public PlayerAirBorneState AirBorneState { get; }
    public PlayerKnockBackState KnockBackState { get; }

    // 생략

    public PlayerStateMachine(Player player)
    {
        // 생략
        
        DeadState = new PlayerDeadState(this);
        AirBorneState = new PlayerAirBorneState(this);
        KnockBackState = new PlayerKnockBackState(this);
    }

    private void Dead()
    {
        ChangeState(DeadState);
    }

    public void TakeEffect(AttackEffectTypes attackEffectTypes, float value, GameObject attacker)
    {
        if (!AffectedEffectInfo.CanBeAffected(attackEffectTypes))
            return;

        switch (attackEffectTypes)
        {
            case AttackEffectTypes.KnockBack:
                Vector3 direction = Player.transform.position - attacker.transform.position;
                direction.Normalize();
                Player.ForceReceiver.AddForce(direction * value);
                ChangeState(KnockBackState);
                break;
            case AttackEffectTypes.Airborne:
                Player.ForceReceiver.AddForce(Vector3.up * value);
                ChangeState(AirBorneState);
                break;
            case AttackEffectTypes.Stun:
                // Stun 로직
                break;
        }
    }
}

 

 

PlayerAnimationDate 수정

public class PlayerAnimationData
{
    // 생략
    
    [SerializeField] private string deadParameterName = "Dead";
    [SerializeField] private string airBorneParameterName = "AirBorne";
    [SerializeField] private string knockBackParameterName = "KnockBack";

    // 생략
    
    public int DeadParameterHash { get; private set; }
    public int AirBorneParameterHash { get; private set; }
    public int KnockBackParameterHash { get; private set; }
    
    public void Initialize()
    {
        // 생략

        DeadParameterHash = Animator.StringToHash(deadParameterName);
        AirParameterHash = Animator.StringToHash(airBorneParameterName);
        KnockBackParameterHash = Animator.StringToHash(knockBackParameterName);
    }
}

 

 

아직 상태머신을 확장만 해놓고 기능연결은 하지 않았다. 내일 수정해야 할 사항들과 함께 같이 작업을 해볼 계획이다.

 

 

 

 

버그리포트

 

그리고 오늘 깃헙을 통해서 협업을 할 때 조심해야 하는 부분에 대해서 또 배운 점이 있었다.

내가 특수능력을 구현하기 때문에 PlayerInputAction에 새로운 키를 할당하여 작업을 했는데

다른 팀원분께서도 다른 키 입력 기능을 추가하셔야 해서 PlayerInputAction스크립트가 자동생성되는 스크립트이기

때문에 작업공간이 겹쳐서 충돌이 있었다.

 

근데 내가 깃헙 웹에서 컨플릭트 해결을 하다가 실수로 팀원분이 작업하신 내용까지 같이 지워버린 건지

다시 메인에서 브랜치를 파서 작업을 하려고 하는데 파일에서 에러가 뜨면서

여태까지 본 적 없던 양의 버그가 한 번에 쏟아져 나와서 굉장히 당혹스러웠었다.

하지만 어제 기능구현을 마치고 합쳤던 브랜치는 그 즉시 삭제를 해버리는 바람에 어디가 덧씌워지고 사라진 건지 확인할 방법도 없었다...

 

내가 머지를 하고 메인에서 다시 새로운 브랜치를 파서 메인의 파일이 문제없이 잘 작동하는지 확인을 했어야 했는데 생각이 짧았다. 하지만 스크립트가 유실되거나 한 게 아니라 그냥 할당된 키만 유실되었기 때문에 혹시나 하고 키를 다시 할당해 주고 실행해 보니 버그가 뜨지 않았다.

아무래도 자동생성해 주는 PlayerInputAction 스크립트에서 내가 실수로 뭔가를 같이 지워버린 바람에 스크립트에서 오류가 떠서 Player를 인식하지 못했던 것 같다. 다음부터는 꼭 잘 확인을 하고 팀원분들과 함께 소통하면서 머지를 해야겠다.