using System.Collections;
using UnityEngine;
public class Stage2Enemy : EnemyBase
{
public float detectionRadius = 5.0f;
public LayerMask playerLayer;
public LayerMask wallLayer;
public float rayHeight = 1.0f;
private GameObject detectedPlayer = null;
public GameObject enemyBulletPrefab;
public Transform firePos;
public float fireForce;
protected override void Update()
{
base.Update(); //EnemyBase의 Update 메서드를 호출
PlayerDetect();
RotateTowardsPlayer();
}
protected override IEnumerator EnemyAnimation()
{
while (true)
{
switch (enemyState)
{
case EnemyState.Idle:
animator.SetInteger("state", 0);
break;
case EnemyState.Walk:
animator.SetInteger("state", 1);
break;
case EnemyState.GetHit:
animator.SetInteger("state", 2);
break;
case EnemyState.Die:
animator.SetInteger("state", 3);
targetPoint.SetActive(false);
this.GetComponent<Collider>().enabled = false;
yield return new WaitUntil(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1f && animator.GetCurrentAnimatorStateInfo(0).IsName("Die"));
Destroy(this.gameObject);
break;
case EnemyState.Attack:
animator.SetInteger("state", 4);
break;
}
yield return null;
}
}
void RotateTowardsPlayer()
{
if (enemyState == EnemyState.Attack && detectedPlayer != null)
{
Vector3 lookDirection = (detectedPlayer.transform.position - transform.position).normalized;
lookDirection.y = 0; // Y축 변경을 방지
Quaternion lookRotation = Quaternion.LookRotation(lookDirection);
transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5.0f); // 부드러운 회전
}
}
void PlayerDetect()
{
if (enemyHP <= 0 || enemyState == EnemyState.GetHit) return; // 체력이 0 이하이거나 피격 상태이면 감지를 하지 않음
Collider[] detectedPlayers = Physics.OverlapSphere(transform.position, detectionRadius, playerLayer);
if (detectedPlayers.Length > 0)
{
detectedPlayer = detectedPlayers[0].gameObject;
Vector3 rayDirection = (detectedPlayer.transform.position - transform.position).normalized;
Vector3 rayStartPosition = transform.position + new Vector3(0, rayHeight, 0);
RaycastHit hit;
if (Physics.Raycast(rayStartPosition, rayDirection, out hit, detectionRadius, playerLayer | wallLayer))
{
if (hit.collider.CompareTag("Player"))
{
enemyState = EnemyState.Attack;
}
else if (hit.collider.gameObject.layer == LayerMask.NameToLayer("Wall"))
{
detectedPlayer = null;
enemyState = EnemyState.Idle;
}
}
else
{
detectedPlayer = null;
enemyState = EnemyState.Idle;
}
}
else
{
detectedPlayer = null;
enemyState = EnemyState.Idle;
}
}
public void FireAtPlayer() //애니메이션 이벤트
{
// 적의 중앙을 향한 방향 계산
Vector3 playerCenter = detectedPlayer.transform.position + new Vector3(0, detectedPlayer.GetComponent<Collider>().bounds.extents.y, 0); //player의 position + Enemy의 콜라이더 높이 절반
Vector3 fireDirection = (playerCenter - firePos.position).normalized;
// 총알 방향을 설정
Quaternion fireRotation = Quaternion.LookRotation(fireDirection);
fireRotation *= Quaternion.Euler(90, 0, 0); // X축으로 90도 회전을 추가
GameObject enemyBullet = Instantiate(enemyBulletPrefab, firePos.position, fireRotation);
Rigidbody rb = enemyBullet.GetComponent<Rigidbody>();
if (rb != null)
{
rb.AddForce(fireDirection * fireForce, ForceMode.Impulse);
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(this.transform.position, detectionRadius);
}
}
using System.Collections;
using UnityEngine;
public abstract class EnemyBase : MonoBehaviour
{
public GameObject targetPoint;
public int enemyHP;
protected Animator animator;
public EnemyState enemyState = EnemyState.Idle;
public enum EnemyState
{
Idle,
Walk,
GetHit,
Die,
Attack
}
protected virtual void Start()
{
animator = GetComponent<Animator>();
targetPoint.SetActive(false);
StartCoroutine(EnemyAnimation());
}
protected virtual void Update()
{
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
if (stateInfo.IsName("GetHit") && stateInfo.normalizedTime >= 1.0f)
{
enemyState = EnemyState.Idle;
}
}
protected virtual void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Bullet"))
{
enemyHP--;
Destroy(other.gameObject);
if (enemyHP <= 0)
{
enemyState = EnemyState.Die;
}
else
{
if (enemyState != EnemyState.GetHit)
{
enemyState = EnemyState.GetHit;
animator.Play("GetHit", -1, 0f);
}
}
}
}
protected abstract IEnumerator EnemyAnimation();
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class PlayerController : MonoBehaviour
{
public Joystick joystick;
public float moveSpeed = 5.0f;
public float detectionRadius = 5.0f; // 적을 탐지하는 범위
public LayerMask enemyLayer;
public LayerMask wallLayer;
public float rayHeight = 1.0f;
private AnimationState currentState;
private Animator animator;
private EnemyBase targetedEnemy = null;
// Ray의 시작점과 끝점을 저장할 변수
private Vector3 rayStart;
private Vector3 rayEnd;
public GameObject bulletPrefab;
public Transform firePos;
public float fireForce = 1000f;
public GameObject targetPlayer;
private Collider playerCollider;
void Start()
{
currentState = AnimationState.Idle;
animator = GetComponent<Animator>();
targetPlayer.SetActive(true);
playerCollider = GetComponent<Collider>();
}
void Update()
{
if (currentState != AnimationState.Die)
{
Move();
DetectEnemies();
}
HandleAnimation();
}
void Move()
{
Vector3 moveDirection = new Vector3(-joystick.Vertical, 0, joystick.Horizontal).normalized;
transform.Translate(moveDirection * moveSpeed * Time.deltaTime, Space.World);
if (moveDirection != Vector3.zero)
{
Quaternion toRotation = Quaternion.LookRotation(moveDirection, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, toRotation, 360f * Time.deltaTime);
currentState = AnimationState.Run;
}
else
{
if (targetedEnemy != null)
{
currentState = AnimationState.Attack;
LookAtEnemy(targetedEnemy.transform);
}
else
currentState = AnimationState.Idle;
}
}
public void FireAtEnemy() //애니메이션 이벤트
{
// 적의 중앙을 향한 방향 계산
Vector3 enemyCenter = targetedEnemy.transform.position + new Vector3(0, targetedEnemy.GetComponent<Collider>().bounds.extents.y, 0); //Enemy의 position + Enemy의 콜라이더 높이 절반
Vector3 fireDirection = (enemyCenter - firePos.position).normalized;
// 총알 방향을 설정
Quaternion fireRotation = Quaternion.LookRotation(fireDirection);
fireRotation *= Quaternion.Euler(90, 0, 0); // X축으로 90도 회전을 추가
GameObject bullet = Instantiate(bulletPrefab, firePos.position, fireRotation);
StartCoroutine(FireEffectCoroutine());
Rigidbody rb = bullet.GetComponent<Rigidbody>();
if (rb != null)
{
rb.AddForce(fireDirection * fireForce, ForceMode.Impulse);
}
}
private IEnumerator FireEffectCoroutine() // 총구 화염 효과 코루틴
{
foreach (Transform child in firePos)
{
child.gameObject.SetActive(true);
}
yield return new WaitForSeconds(0.2f);
foreach (Transform child in firePos)
{
child.gameObject.SetActive(false);
}
}
void DetectEnemies()
{
Vector3 rayDirection;
Vector3 rayStartPosition = transform.position + new Vector3(0, rayHeight, 0);
// 타겟이 범위를 벗어났는지 확인
if (targetedEnemy != null)
{
float distanceToTarget = Vector3.Distance(transform.position, targetedEnemy.transform.position);
if (distanceToTarget > detectionRadius || !targetedEnemy.targetPoint.activeSelf)
{
targetedEnemy.targetPoint.SetActive(false);
targetedEnemy = null;
}
}
if (targetedEnemy == null)
{
Collider[] detectedEnemies = Physics.OverlapSphere(transform.position, detectionRadius, enemyLayer);
EnemyBase closestVisibleEnemy = null;
float minDistance = float.MaxValue;
foreach (Collider collider in detectedEnemies)
{
rayDirection = (collider.transform.position - transform.position).normalized;
RaycastHit hit1;
// Ray 발사
if (Physics.Raycast(rayStartPosition, rayDirection, out hit1, detectionRadius, enemyLayer | wallLayer))
{
if (hit1.collider.GetComponent<EnemyBase>())
{
float distance = Vector3.Distance(transform.position, collider.transform.position);
if (distance < minDistance)
{
closestVisibleEnemy = collider.GetComponent<EnemyBase>();
minDistance = distance;
}
}
}
}
if (closestVisibleEnemy && closestVisibleEnemy.enemyState != EnemyBase.EnemyState.Die)
{
rayDirection = (closestVisibleEnemy.transform.position - transform.position).normalized;
if (targetedEnemy != null)
targetedEnemy.targetPoint.SetActive(false);
targetedEnemy = closestVisibleEnemy;
targetedEnemy.targetPoint.SetActive(true);
}
else
{
return;
}
}
else
{
rayDirection = (targetedEnemy.transform.position - transform.position).normalized;
}
RaycastHit hit2;
if (Physics.Raycast(rayStartPosition, rayDirection, out hit2, detectionRadius, enemyLayer | wallLayer))
{
rayStart = rayStartPosition;
rayEnd = hit2.point;
if (hit2.collider.gameObject.layer == LayerMask.NameToLayer("Wall"))
{
if (targetedEnemy != null)
{
targetedEnemy.targetPoint.SetActive(false);
targetedEnemy = null;
}
}
}
}
void LookAtEnemy(Transform enemyTransform)
{
Vector3 lookDirection = enemyTransform.position - transform.position;
lookDirection.y = 0;
Quaternion rotation = Quaternion.LookRotation(lookDirection);
transform.rotation = rotation;
}
void HandleAnimation()
{
animator.SetInteger("state", (int)currentState);
}
private void OnCollisionEnter(Collision collision) // Enemy와 직접적인 충돌
{
if (collision.gameObject.CompareTag("Enemy"))
{
GameManager.Instance.PlayerHP -= 1; // HP 감소
Debug.Log("Player HP: " + GameManager.Instance.PlayerHP);
if (GameManager.Instance.PlayerHP <= 0)
{
currentState = AnimationState.Die;
targetPlayer.SetActive(false);
playerCollider.enabled = false;
}
}
}
private void OnTriggerEnter(Collider other) // Enemy 투사체에 의한 충돌
{
// EnemyBullet과 충돌 시
if (other.gameObject.CompareTag("EnemyBullet"))
{
GameManager.Instance.PlayerHP -= 1;
Destroy(other.gameObject);
Debug.Log("Player HP: " + GameManager.Instance.PlayerHP);
// HP가 0이면 Die 상태로 전환
if (GameManager.Instance.PlayerHP <= 0)
{
currentState = AnimationState.Die;
targetPlayer.SetActive(false);
playerCollider.enabled = false;
//Destroy(gameObject, 3f); // 3초 후에 Player를 Destroy (Die 애니메이션이 끝날 때까지 대기)
}
else
{
currentState = AnimationState.GetHit;
}
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(this.transform.position, detectionRadius);
// 저장된 Ray의 정보를 기반으로 선을 그림
Gizmos.color = Color.red;
Gizmos.DrawLine(rayStart, rayEnd);
}
}
public enum AnimationState
{
Idle,
Run,
Attack,
Reload,
GetHit,
Die
}
[구현 목록]
1. Enemy 스크립트들을 EnemyBase 클래스를 상속받게 구성해서 Player 스크립트 단일화
2. Enemy와 Player의 발사체 궤적 구현
3. Player의 총구 화염 효과 구현
4. Stage2 Enemy 구현 (Player 인식 및 회전, Attack시 발사체 구현)
'KDT > 3D 콘텐츠 제작' 카테고리의 다른 글
[HeroShooter] 스테이지4: 움직이고 근거리 공격하는 몬스터 배치 구현 (0) | 2023.08.31 |
---|---|
[HeroShooter] 벽(콜라이더)에 부딫히면 떨리는 문제 해결 (0) | 2023.08.29 |
[HeroShooter] Stage2. Enemy의 피격과 사망, 인식 구현 (0) | 2023.08.24 |
[HeroShooter] Ray와 Overlap을 이용한 적 감지 구현 (0) | 2023.08.23 |
[HeroShooter] Tutorial UI 및 트리거 구현 (0) | 2023.08.22 |