- 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"); // 공격 중지 트리거 실행
}
}
}
- 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); // 지정된 목적지로 이동 설정
}
}
- 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 반환
}
}
- 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;
}
}
이 클래스는 동시에 여러 액션이 실행되는 것을 방지하는 역할을 합니다.
유니티 Hp 관리 (0) | 2024.09.20 |
---|---|
유니티 공격 애니메이션 딜레이 만들기 (0) | 2024.09.20 |
유니티 캐릭터 내비게이션 이동 및 공격 기본 세팅 (0) | 2024.09.19 |
유니티 마우스 클릭 위치로 캐릭터 이동하기, 카메라가 캐릭터 따라다니기 (1) | 2024.09.18 |
유니티 마우스로 게임화면 클릭한 위치를 카메라로부터 선 그리기 (2) | 2024.09.18 |