[컴퓨터]/웹 | 앱 | 게임 개발

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 13회차 미션

BOOX 2020. 10. 31. 01:40

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 13회차 미션 시작합니다.




어느덧 챕터 5 진입이네요.

전투 시스템에 대하여 알아보는 시간입니다.

배우게 될 전체 구성 순서는 다음과 같습니다.

1. 전투 시스템 구성 알아보기
2. 전투 시스템 구현하기
3. 근접 전투 로직 구현하기
4. 원거리 전투 로직 구현하기
5. NPC의 HP UI 구현하기




+ EnemyController는 기존에 구현하였던 플레이어 캐릭터나 적캐릭터라고 보면 됩니다. 여기서는 Idle, Attack, Move State만 가지는 Controller를 구현합니다.
+ AttackStateController는 Animator에서 공격중인지 상태를 알게해주고, 이를 확인하여 EnemyController Logic에 전달하여 상태를 확인하도록 구현합니다. 물론 EnemyController 컴포넌트에서 직접 작업하여도 되지만, AttackStateController를 재활용할 수 있도록 별도의 컴포넌트로 제작을 하는 것입니다.
+ IAttackable은 Animator에서 공격이 발생하는 시점에 호출이되는 이벤트 인터페이스입니다. IAttackable 인터페이스를 따로 구현한 이유 역시 공격가능한 GameObject에 쉽게 부착하여 구현할 수 있도록 구성한 것입니다. 정말 좋은 내용입니다.
+ IDamageable도 마찬가지이지요. 데미지를 받아 에너지가 줄어드는 GameObject인 경우 적용합니다.
인터페이스의 장점은 클래스 상속과 상관없이 Attach/Dettach가 가능하고, 식별이 용이하다는 것이 장점입니다. 단점이라면 구조가 복잡해질 수 있다는 점입니다. Interface를 컴포넌트로 구성할 수도 있지만 참조가 많이 발생하게 되어 복잡성이 높아질 수 있어서 여기서는 Interface로만 구현합니다.
+ AttackBehaviour_xx는 공격 종류라고 보면 됩니다. EnemyController에서 여러 공격의 종류 중 하나를 선택하여 공격이 발생하는 시스템입니다.


AttackStateController와 Animator를 먼저 구성해 봅니다.



StateMachine은 위와 같이 구성합니다.


AttackStateController.cs

public class AttackStateController : MonoBehaviour
{
    public delegate void OnEnterAttackState();
    public delegate void OnExitAttackState();
    
    public OnEnterAttackState enterAttackStateHandler;
    public OnExitAttackState exitAttackStatehandler;
    
    public bool IsInAttackState
    {
        get;
        private set; // 외부에서 값 설정 불가.
    }
    
    void Start()
    {
        enterAttackStateHandler = new OnEnterAttackState(EnterAttackState);
        exitAttackStateHandler = new OnExitAttackState(ExitAttackState);
    }
    
    public void OnStartOfAttackState()
    {
        IsInAttackState = true;
        enterAttackStateHandler();
    }
    
    public void OnEndOfAttackState()
    {
        IsInAttackState = false;
        exitAttackStateHandler();
    }
    
    private void EnterAttackState()
    {
    }
    
    private void ExitAttackState()
    {
    }
    
    // Animator에서 호출할 함수
    public void OnCheckAttackCollider(int attackIndex)
    {
        GetComponent<IAttackable>()?.OnExecuteAttack(attackIndex);
    }
}

 



AttackStateMachineBehaviour.cs

public class AttackStateMachineBehaviour : StateMachineBehaviour
{
    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        animator.getObject.GetComponent<AttackStateController>()?.OnStartOfAttackState();
    }
    
    public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        animator.getObject.GetComponent<AttackStateController>()?.OnEndOfAttackState();
    }
}

 



IAttackable.cs

public interface IAttackable
{
    AttackBehaviour CurrentAttackBehaviour
    {
        get;
    }
    
    void OnExecuteAttack(int attackIndex);
}


IDamageable.cs

public interface IDamageable
{
    bool IsAlive
    {
        get;
    }
    
    void TakeDamage(int damage, GameObject hitEffectPrefabs);
}



AttackBehaviour.cs

public abstract class AttackBehaviour : MonoBehaviour
{
// 개발자가 어떤 컴포넌트인지 확인할 수 있도록 주석 만들기.
#if UNITY_EDITOR
    [Multiline]
    public string developmentDescription = "";
#endif

    public int animationIndex;
    public int priority; // 여러 공격중 어떤 것을 선택할지에 대한 우선순위
    public int damage = 10;
    public float range = 3f;
    
    [SerializedField]
    protected float coolTime; // 공격후 대기 시간?
    protected float calcCoolTime; = 0.0f;
    
    public GameObject effectPrefab;
    
    [HideInInspector]
    public LayerMask targetMask;
    
    void Start()
    {
        calcCoolTime = coolTime; // 바로 공격 가능 상태
    }
    
    void Update()
    {
        if (calcCoolTime < coolTime)
        {
            calcCoolTime += Time.deltaTime;
        }
    }
    
    // startPoint는 발사체가 발사되는 지점용
    public abstract void ExecuteAttack(GameObject target = null, Transform startPoint = null);
}

 


EnemyController_New.cs

public EnemyController_New : EnemyController, IAttackable, IDamageable
{
    public Transform projectilePoint;
    
    [SerializedField]
    private List<AttackBehaviour> attackBehavious = new List<AttackBehaviour>();
    
    protected override void Start()
    {
        base.Start();
        stateMachine.AddState(new MoveState());
        stateMachine.AddState(new AttackState());
        stateMachine.AddState(new DeadState());
        
        InitAttackBehaviour();
    }
    
    protected override void Update()
    {
        CheckAttackBehaviour();
        base.Update();
    }
    
    private void InitAttackBehaviour()
    {
        foreach (AttackBehaviour behaviour in attackBehavious)
        {
            if (CurrentAttackBehaviour == null)
                CurrentAttackBehaviour = behaviour;
                
            behaviour.targetMask = TargetMask;
        }
    }
    
    private void CheckAttackBehaviour()
    {
        if (CurrentAttackBehaviour == null || !CurrentAttackBehaviour.IsAvailable)
        {
            CurrentAttackBehaviour = null;
            foreach (AttackBehaviour behaviour in attackBehavious)
            {
                if (behaviour.IsAvailable)
                {
                    // 가장 우선순위가 높은 공격 설정
                    if (CurrentAttackBehaviour == null || CurrentAttackBehaviour.priority < behaviour.priority)
                    {
                        CurrentAttackBehaviour = behaviour;
                    }
                }
            }
        }
    }
    
    public AttackBehaviour CurrentAttackBehaviour
    {
        get;
        private set; // 내부에서만 set 가능하도록..
    }
    
    public void OnExecuteAttack(int attackIndex)
    {
        if (CurrentAttackBehaviour != null && Target != null)
        {
            CurrentAttackBehaviour.ExecuteAttack(Target.gameObject, projectilePoint); // Target은 공격가능 거리내에 있는 타겟중 가장 가까운 것
        }
    }
}

 

 



적의 Animation 구동중에 OnCheckAttackCollider() 함수를 호출하도록 구현되어 있습니다.

캐릭터의 전투 시스템 구성은 C# 코딩으로 모두 이루어진다고 할 수 있겠네요.
이젠 배우는 내용들이 많아지므로 이전에 배운 내용은 코드 재사용을 하므로 정말 빠르게 진행됩니다.
이번에 배운 중요한 부분은 IAttackable이라고 봐야겠네요. 다음시간에는 IDamageable에 대해 구현하면 마무리가 될 것 같습니다.



<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr