상세 컨텐츠

본문 제목

유니티 RPG 초반 코드 (Basic Combat)

유니티/기능

by MJ_119 2024. 9. 19. 23:28

본문

- Fighter 

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

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        // 무기 사정거리, 공격 사이의 시간, 무기 데미지 설정
        [SerializeField] float weaponRange = 2f;
        [SerializeField] float timeBetweenAttack = 1f;
        [SerializeField] float weaponDamage = 5f;

        // 마지막 공격 후 경과 시간 저장 변수
        float timeSinceLastAttack = 0f;
        Health targetEnemy; // 현재 타겟으로 설정된 적의 Health 컴포넌트를 참조
        PlayerMove playerMove; // 플레이어 이동을 제어하는 스크립트
        Animator animator; // 캐릭터 애니메이터

        // 시작 시 컴포넌트들을 가져옴
        void Start()
        {
            playerMove = GetComponent<PlayerMove>();
            animator = GetComponent<Animator>();
        }

        // 매 프레임마다 호출되는 업데이트 함수
        void Update()
        {
            // 경과 시간을 증가
            timeSinceLastAttack += Time.deltaTime;

            // 타겟이 없다면 함수 종료
            if (targetEnemy == null) return;

            // 타겟이 죽었다면 함수 종료
            if (targetEnemy.IsDead)
            {
                return;
            }

            // 타겟이 사정거리 밖에 있으면 타겟을 향해 이동
            if (!GetIsInRange())
            {
                playerMove.MoveTo(targetEnemy.transform.position);
            }
            // 사정거리 안에 있으면 공격 준비
            else
            {
                playerMove.Cancle(); // 이동 중지
                AttackAnimation();   // 공격 애니메이션 실행
            }
        }

        // 공격 애니메이션 실행 함수
        private void AttackAnimation()
        {
            transform.LookAt(targetEnemy.transform); // 공격 전에 적을 바라보도록 설정

            // 적이 죽었으면 공격 중단
            if (targetEnemy.IsDead) return;

            // 공격 시간 간격을 만족하면 공격 실행
            if (timeSinceLastAttack >= timeBetweenAttack)
            {
                TriggerAttack(); // 공격 트리거 실행
                timeSinceLastAttack = 0f; // 공격 간격 초기화
            }
        }

        // 애니메이터의 공격 트리거 설정
        private void TriggerAttack()
        {
            animator.ResetTrigger("stopAttack"); // 이전의 공격 중지 트리거 초기화
            animator.SetTrigger("attack"); // 공격 트리거 실행
        }

        // 실제 공격 시 타격 처리 함수
        public void Hit()
        {
            if (targetEnemy == null) return; // 타겟이 없으면 함수 종료
            targetEnemy.TakeDamaged(weaponDamage); // 타겟에게 데미지를 줌
        }

        // 공격 가능 여부 확인 함수
        public bool CanAttack(CombatTarget combatTarget)
        {
            if (combatTarget == null) return false; // 타겟이 없으면 false 반환
            Health targetToTest = combatTarget.GetComponent<Health>(); // 타겟의 Health 컴포넌트 확인
            return targetToTest != null && !targetToTest.IsDead; // 타겟이 살아있으면 공격 가능
        }

        // 적과의 거리 비교하여 사정거리 안에 있는지 확인
        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, targetEnemy.transform.position) < weaponRange;
        }

        // 공격 함수 (CombatTarget을 받아서 공격 시작)
        public void Attack(CombatTarget combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this); // 액션 스케줄러에서 이 액션 시작
            targetEnemy = combatTarget.GetComponent<Health>(); // 타겟의 Health 컴포넌트 설정
        }

        // 공격 취소 함수
        public void Cancle()
        {
            StopAttack(); // 공격 중지 애니메이션 실행
            targetEnemy = null; // 타겟 초기화
        }

        // 공격 중지 애니메이션 트리거 설정
        private void StopAttack()
        {
            animator.ResetTrigger("attack"); // 공격 트리거 초기화
            animator.SetTrigger("stopAttack"); // 공격 중지 트리거 실행
        }
    }
}

 

  • 변수 설명: 무기 사정거리, 공격 간격, 공격력 설정 및 경과 시간 관리 변수를 설정.
  • Start(): 필요한 컴포넌트를 가져오는 초기화 작업.
  • Update(): 매 프레임마다 타겟과의 거리, 적의 생존 여부를 체크하여 공격 실행 여부 결정.
  • AttackAnimation(): 타겟을 향해 공격 애니메이션을 실행하고, 공격 사이의 간격을 조정.
  • TriggerAttack(): 애니메이터의 공격 트리거를 설정하여 공격 모션을 시작.
  • Hit(): 실제로 적에게 데미지를 가하는 함수.
  • CanAttack(): 타겟이 공격 가능한지 확인.
  • GetIsInRange(): 적과의 거리를 계산하여 사정거리 내에 있는지 확인.
  • Attack(): 공격을 시작하는 함수.
  • Cancle(): 공격을 취소하고, 타겟을 초기화하는 함수.

 

 

 

- PlayerMove

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

public class PlayerMove : MonoBehaviour, IAction
{
    NavMeshAgent navMeshAgent;
    Animator animator;
    Fighter fighter;

    void Start()
    {
        navMeshAgent = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
        fighter = GetComponent<Fighter>();
    }

    void Update()
    {
        // 매 프레임마다 애니메이터의 속도를 업데이트
        UpdateAnimator();
    }

    // 현재 플레이어의 속도를 기반으로 애니메이터의 'speed' 파라미터를 업데이트
    private void UpdateAnimator()
    {
        Vector3 velocity = navMeshAgent.velocity; // 현재 이동 속도를 가져옴
        Vector3 localVelocity = transform.InverseTransformDirection(velocity); // 월드 좌표 기준 속도를 로컬 좌표로 변환
        float speed = localVelocity.z; // 전진 속도를 가져옴
        animator.SetFloat("speed", speed); // 애니메이터에 속도를 전달해 애니메이션 처리
    }

    // 이동을 취소하는 메서드 (NavMeshAgent를 멈춤)
    public void Cancle()
    {
        navMeshAgent.isStopped = true; // NavMeshAgent가 멈추도록 설정
    }

    // 새로운 이동을 시작하는 메서드
    public void StartMoveAction(Vector3 destination)
    {
        // 다른 액션을 취소하고 현재 액션(PlayerMove)으로 설정
        GetComponent<ActionScheduler>().StartAction(this);
        MoveTo(destination); // 이동 메서드를 호출
    }

    // 지정된 위치로 이동하는 메서드
    public void MoveTo(Vector3 destination)
    {
        navMeshAgent.isStopped = false; // NavMeshAgent가 멈추지 않도록 설정
        navMeshAgent.SetDestination(destination); // 지정된 목적지로 이동 설정
    }

}

 

  • UpdateAnimator():
    • 플레이어의 속도에 따라 애니메이션 속도를 업데이트합니다. 현재의 월드 좌표 기준 속도를 로컬 좌표로 변환한 후, 캐릭터가 앞으로 이동하는 속도를 애니메이션에 반영해 자연스러운 움직임을 만듭니다.
  • Cancle():
    • 플레이어가 이동 중인 경우 그 움직임을 멈추도록 합니다.
  • StartMoveAction():
    • ActionScheduler를 통해 다른 액션을 취소하고 현재 이동 액션을 실행합니다. 그리고 MoveTo()를 호출해 캐릭터를 이동시킵니다.
  • MoveTo():
    • 지정된 좌표로 플레이어를 이동시키는 메서드입니다. NavMeshAgent를 사용해 목적지로 경로를 계산하고 이동을 처리합니다.

 

 

- CombatTarget

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

[RequireComponent(typeof(Health))]
public class CombatTarget : MonoBehaviour
{

}

 

- PlayerController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Combat;

public class PlayerController : MonoBehaviour
{
    Fighter fighter; // Fighter 컴포넌트를 저장하는 변수

    private void Start()
    {
        // Fighter 컴포넌트를 가져옴
        fighter = GetComponent<Fighter>();
    }

    void Update()
    {
        // 공격이 가능하면 공격 후 이동 코드를 실행하지 않음.
        if (MouseClickToCombat()) return;

        // 이동이 가능하면 이동 후 다른 코드를 실행하지 않음.
        if (MouseClickToMove()) return;

        // 공격이나 이동이 불가능하면 "Can't move" 메시지를 출력하려 했으나 현재는 주석 처리됨.
        // print("Can't move");
    }

    // 마우스 클릭으로 공격을 처리하는 메서드
    private bool MouseClickToCombat()
    {
        // 마우스 위치를 기준으로 레이를 발사
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hits = Physics.RaycastAll(ray); // 발사된 레이에 닿은 모든 오브젝트를 가져옴

        // 레이에 닿은 모든 오브젝트들을 하나씩 검사
        foreach (RaycastHit hit in hits)
        {
            // CombatTarget 컴포넌트가 있는지 확인. 없으면 다음 오브젝트로 넘어감.
            CombatTarget target = hit.transform.GetComponent<CombatTarget>();
            if (!fighter.CanAttack(target))
            {
                continue; // 공격할 수 없으면 다음 오브젝트로 이동
            }

            // 마우스 왼쪽 버튼을 클릭했을 때만 공격 실행
            if (Input.GetMouseButtonDown(0))
            {
                fighter.Attack(target); // 공격 실행
            }
            return true; // 공격 성공 시 true 반환
        }
        return false; // 공격할 대상이 없으면 false 반환
    }

    // 마우스 클릭으로 이동을 처리하는 메서드
    private bool MouseClickToMove()
    {
        // 마우스 위치를 기준으로 레이를 발사
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        bool hasHit = Physics.Raycast(ray, out hit); // 레이가 오브젝트에 맞았는지 확인
        if (hasHit)
        {
            // 마우스 왼쪽 버튼을 누른 상태일 때만 이동 실행
            if (Input.GetMouseButton(0))
            {
                // 클릭한 지점으로 이동을 시작
                GetComponent<PlayerMove>().StartMoveAction(hit.point);
            }
            return true; // 이동 성공 시 true 반환
        }
        return false; // 이동할 대상이 없으면 false 반환
    }
}

 

 

  • fighter 변수 선언 및 초기화:
    • Start()에서 Fighter 컴포넌트를 가져와 초기화함. 이후에 공격 로직에서 사용됩니다.
  • Update() 메서드:
    • 매 프레임마다 호출되며, MouseClickToCombat()에서 공격이 성공하면 이후의 코드를 실행하지 않음.
    • 공격이 불가능하면 MouseClickToMove()로 이동 처리를 실행.
    • 두 메서드 모두 성공하면 true를 반환하여 다른 행동이 중단되게 함.
  • MouseClickToCombat():
    • 마우스 위치에서 레이를 발사하여 CombatTarget이 있는지 확인.
    • 공격 가능한 상태인지 확인 후, 마우스 왼쪽 버튼이 눌리면 공격을 실행.
  • MouseClickToMove():
    • 마우스 위치에서 레이를 발사하여 이동 가능한 지점을 찾음.
    • 마우스 왼쪽 버튼이 눌리면 해당 지점으로 이동을 시작.

 

 

- FollowCam

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

public class FollowCam : MonoBehaviour
{
    [SerializeField] Transform target;
    void Update()
    {
        transform.position = target.position;
    }
}

 

- Health

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

public class Health : MonoBehaviour
{
    [SerializeField] float health = 100f;  // 초기 체력 값, 인스펙터에서 설정 가능

    bool isDead = false;  // 캐릭터가 죽었는지 여부를 저장하는 변수
    public bool IsDead { get { return isDead; } }  // 죽음 상태를 외부에서 읽을 수 있는 프로퍼티

    // 데미지를 받을 때 호출되는 메서드
    public void TakeDamaged(float damage)
    {
        // 체력이 0 이하로 떨어지지 않도록 MathF.Max로 최소값을 0으로 설정
        health = MathF.Max(health - damage, 0);
        print(health);  // 현재 체력 값 출력 (디버깅용)

        // 체력이 0이면 죽음 처리 메서드 호출
        if (health == 0)
        {
            Die();
        }
    }

    // 캐릭터가 죽었을 때 호출되는 메서드
    private void Die()
    {
        // 이미 죽었다면 추가로 Die()가 호출되지 않도록 방지
        if (isDead) return;

        // 캐릭터를 죽음 상태로 변경
        isDead = true;

        // Animator를 가져와서 'die' 트리거 발동 (죽음 애니메이션 재생)
        Animator animator = GetComponent<Animator>();
        animator.SetTrigger("die");

        // 죽음 상태를 출력 (디버깅용)
        print(isDead);
    }
}

 

 

 

- ActionScheduler

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

public class ActionScheduler : MonoBehaviour
{
    IAction currentAction; // 현재 실행 중인 액션을 저장하는 변수

    // 새로운 액션을 시작하는 메서드
    public void StartAction(IAction action)
    {
        // 현재 액션이 새로 들어온 액션과 같으면 그대로 유지하고 리턴
        if (currentAction == action) return;

        // 현재 액션이 존재하면 취소(Cancle) 메서드를 호출해 기존 액션 중지
        if (currentAction != null)
        {
            currentAction.Cancle();
        }

        // 새 액션을 currentAction에 할당하여 현재 실행할 액션으로 설정
        currentAction = action;
    }
}

 

  1. currentAction:
    • 현재 진행 중인 액션을 추적하는 변수입니다. IAction 인터페이스를 구현한 객체만 저장할 수 있습니다.
  2. StartAction(IAction action):
    • 새로운 액션을 시작하는 메서드입니다.
    • 새로운 액션이 들어왔을 때, 이전에 실행 중인 액션과 동일한지 확인합니다. 같으면 이미 그 액션이 실행 중이므로 별도로 중지하지 않고 그대로 유지합니다.
    • 실행 중이던 액션이 존재하면, 그 액션을 취소하고 새로 들어온 액션을 실행할 수 있도록 준비합니다.
    • 이후, 새로 전달된 액션을 currentAction으로 할당하여 현재 실행 중인 액션을 업데이트합니다.

이 클래스는 동시에 여러 액션이 실행되는 것을 방지하는 역할을 합니다.

 

 

 

 

관련글 더보기