[유니티] 개인프로젝트 개요

YongSeok·2022년 10월 7일
0

이 글에서는 현재 진행중인 개인 프로젝트의 코드들을 리뷰를 해보려 한다
캐릭터파트, 스킬파트 크게 두개로 나뉘어 리뷰를 해보려 한다.

📌 캐릭터

👇 캐릭터 Class UML


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

public class Interactable : MonoBehaviour
{
    public interactType currentInteractType;
    public allyType currentAllyType;
    
    public virtual void Interaction(Interactable obj)
    {        

    }    

    public virtual void ClaimInteract(Interactable from)
    {

    }

    public virtual interactType CheckInteractTypes(Interactable other)
    {
        if(other == this)
        {
            return interactType.없음;
        }
        else if(CheckAllyType(other))
        {
            return other.currentInteractType;
        }
        else    
        {
            return interactType.공격;
        };

    }
    
    public virtual bool CheckAllyType(Interactable type)
    {
        if (this.currentAllyType == allyType.작동 || type.currentAllyType == allyType.작동)
        {
            return true; 
        }
        if (this.currentAllyType == allyType.파괴물 || type.currentAllyType == allyType.파괴물)
        {
            return false; 
        }
        return this.currentAllyType == type.currentAllyType;
    }
}
  • 모든 상호작용대상(플레이어, 몬스터, 보스, 아이템, npc, 장치물 등)은 각각의 동맹타입과 상호작용 타입을 가지고 있으며 이를 통해 상호작용하는 대상과의 행동을 정한다
  • 예를들어 플레이어가 상호작용 하는 대상이 interactType.공격 이라면 플레이어는 공격을 할것이며 그렇지 않을경우 다른행동을 할 것이다.

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

[RequireComponent(typeof(NavMeshAgent))]
public class Movable : Interactable
{
    public NavMeshAgent agent;
    public Vector3 moveLocation;
    
    public virtual void  Start()
    {
        agent = GetComponent<NavMeshAgent>();
    }
    
    public virtual void MoveTo(Vector3 destination)
    {   if (gameObject.GetComponent<NavMeshAgent>().enabled)
        {
            
            agent.SetDestination(destination);
            moveLocation = destination;
        }
    }

    public virtual void Stop()
    {
        if (gameObject.GetComponent<NavMeshAgent>().enabled)
        {
            agent.SetDestination(transform.position);
        }
    }
}
  • 이동은 NavMeshAgent 기능을 활용하였으며 Controller 클래스에서 MoveTo 함수를 호출시켜주어 캐릭터의 이동을 제어한다.

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

public class Character : Movable, Hittable
{
    [Header("생명력 부분")]
    public int healthCurrent; 
    public int healthMax;

    [Header("마나 부분")]
    public float manaCurrent; 
    public float manaMax;

    [Header("공격 부분")]
    public float attackRange; 
    public int attackDamage; 
    public float attackCoolTime; 
    private float time = 0f;

    [Header("상호작용 부분")]
    public float pickUpRange; 
    public float interactRange;

    [Header("애니메이터 입력 부분")]

    public CharacterAnimator anim; 

    [Header("캐릭터의 주목")]
    public Interactable focusTarget; 
    public interactType focusInteraction;


    [Header("무적 상태")]
    public bool isInvincible = false;

    [Header("스킬")]
    public List<SkillBase> skillList           = new List<SkillBase>();
    public List<CharacterAction> actionList    = new List<CharacterAction>();
    int currentActionIndex = -4;

   
    public List<float> skillCoolList           = new List<float>();
   

    public override void Start()
    {
        base.Start();
        anim = GetComponentInChildren<CharacterAnimator>();
        if(anim)
        {
            anim.owner = this;
        }

    }

    public override void Interaction(Interactable obj) 
    {
        
        focusInteraction = CheckInteractTypes(obj);
        focusTarget = obj;

        switch (focusInteraction)
        {
            case interactType.없음:
                focusTarget = null;
                break;
            case interactType.획득:
                interactRange = pickUpRange;
                break;
            case interactType.공격:
                interactRange = attackRange;
                break;
        };
    }

    protected virtual void Update()
    {
        
        if (time > 0)
        {
            time -= Time.deltaTime;
        }
        for (int i = 0; i < skillCoolList.Count; i++)
        {
            skillCoolList[i] -= Time.deltaTime;
        }


        if (actionList.Count > 0)
        {
            if(currentActionIndex < 0)
            {
                currentActionIndex = 0;
                actionList[currentActionIndex].OnStart(this);
            }
            if (actionList[currentActionIndex].isEnd)
            {
                actionList[currentActionIndex].OnExit(this);
                currentActionIndex++;

                if(currentActionIndex >= actionList.Count)
                {
                    actionList.Clear();
                    currentActionIndex = -4;
                }
                else
                {
                    actionList[currentActionIndex].OnStart(this);
                }
            }
            else
            {
                actionList[currentActionIndex].OnUpdate(this);
            }
        }


        if (focusTarget != null)    
        {
            float distance = Vector3.Distance(transform.position, focusTarget.transform.position); 
            if (distance < interactRange)
            {
                //transform.LookAt(focusTarget.transform.position);
                if (focusInteraction == interactType.공격)
                {

                    //transform.LookAt(focusTarget.transform.position);
                    if (time <= 0)
                    {
                        anim?.IdleMotion();
                        transform.LookAt(focusTarget.transform.position);
                        anim?.AttackMotion();
                        
                        time = attackCoolTime;                        
                    }
                }
                else if ( focusInteraction == interactType.대화 || focusInteraction == interactType.획득)
                {
                    focusTarget.ClaimInteract(this);
                }
                
                Stop();
            }
            else
            {
                base.MoveTo(focusTarget.transform.position);
                anim?.MoveMotion();
            }
        }
        else
        {
            if (Vector3.Distance(transform.position, moveLocation) <= 0.7f)
            {
                Stop();
            }
        }
    }

    

    public virtual void Attack()
    {
        focusTarget.GetClass<Character>()?.GetHit(attackDamage);
        focusTarget = null;
    }

    public virtual int GetHit(int damage)
    {
        if (isInvincible)
        {
            return 0;
        }

        healthCurrent -= damage;
        return damage;
    }

    public override void MoveTo(Vector3 destination)
    {
        base.MoveTo(destination);
        anim?.MoveMotion();
    }

    public override void Stop()
    {
        base.Stop();
        anim?.IdleMotion();
    }

    public virtual void UseSkill(int index)
    {
        if (actionList.Count > 0)
        {
            return;
        }

        if (index < 0 || index >= skillList.Count)
        {
            return;
        }

        SkillBase currentSkill = skillList[index];

        if (manaCurrent >= currentSkill.mana && GetSkillCool(index) <= 0)
        {// [1]
            SetSkillCool(index, currentSkill.coolDown);
            actionList = currentSkill.GetActionClone();
            manaCurrent -= currentSkill.mana;
        }

    }



    public void ActivateSkill(SkillBase skillName)
    {
        actionList.AddRange(skillName.GetActionClone());
    }


    public virtual float SetSkillCool(int index, float wantCoolTime)
    {
        if (index < 0 || index > skillList.Count)
        {
            return 0f;
        }
        while (index >= skillCoolList.Count)
        {
            skillCoolList.Add(0);
        }
        return skillCoolList[index] = wantCoolTime;
    }

    public virtual float GetSkillCool(int index)
    {
        if (index < 0 || index > skillList.Count)
        {
            return 0f;
        }
        while (index >= skillCoolList.Count)
        {
            skillCoolList.Add(0);
        }
        return skillCoolList[index];
    }

    public virtual float GetSkillPercent(int index)
    {
        float currentLeft = GetSkillCool(index);

        if (currentLeft <= 0)
        {
            return 0;
        }

        else
        {
            return skillCoolList[index] / skillList[index].coolDown;
        }
    }

    public virtual void ColliderOn()
    {
    }
    public virtual void ColliderOff()
    {
    }

    
}
  • 캐릭터 클래스를 상속할 플레이어, 몬스터 등의 기본적인 스탯들이 작성된 스크립트이며 필요할시 자식 클래스에서 재정의 할수있게 virtual 키워드를 사용하였다.
  • PlayerController 스크립트와 MonsterController 스크립트에서 Interaction 함수를 호출하여 해당 상황에 맞게 focusTarget을 정한다
  • Update함수에서는 focusTarget이 정해졌을때 실행되는 조건문들이 매 프레임마다 작동중이며 focusTarget이 누군지 에따라 실행되는 결과들이 다 다르다
  • 공격과 히트와 이동이 모두 캐릭터 스크립트에있으며 필요시 자식클래스에서 오버라이딩을 통하여 재정의한다
  • Controller 스크립트에서 스킬을 사용하게 될경우도 이곳에서 리스트에 추가된 count를 통하여 조건문이 발동해 리스트 내부에있는 스킬 리스트들을 순차적으로 실행하며 그와 동시에 스킬 쿨타임도 동시에 관리된다.

0개의 댓글