[SKKU DT] 12일차 -FPS 게임 만들기(3) -적 만들기, 적 구현

2023. 11. 14. 17:18SKKU DT

728x90
반응형

적 상태 설정

 

 


 

 

적 생성 -Capsule Collider 제거, Character Controller 추가

 

 


 

 

적 스크립트 추가 -EnemyFSM 스크립트 생성

enum으로 상태 생성, switch문으로 상태 구별

플레이어가 적의 일정 거리 안에 들어오면 인식하고 쫓아온다.

 

 

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                //Move();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
}

Idle 함수를 만들어서 Debug.Log로 범위 내에 들어오면 Console에 "발견!" 메시지가 호출되는지 확인한다.

 

 


 

 

AttackDistance 생각하기, 적 이동

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
}

일정 거리 계산과 적 공격에 딜레이를 부여

 

 


 

 

응용(미니맵 추가)

https://yoonstone-games.tistory.com/111

 

[Unity] 미니맵 원하는 모양으로 만들기 (+컬링마스크)

이번 시간에는 게임의 완성도를 높여주는 미니맵을 원하는 모양으로 만드는 방법을 배워보도록 하겠습니다 :) 1. 미니맵을 만들기 위한 준비 바닥(Plane)과 플레이어(Capsule)를 만들어 배치하고, 플

yoonstone-games.tistory.com

미니맵 용 카메라 추가

프로젝트 안에 Render Texture를 하나 만든 후 미니맵 카메라 안에 [Target Texture]에 넣는다.

캔버스 - Raw Image를 만든 후 [Texture]에 만들었던 Render Texture를 넣는다.

 

 

미니맵에 크게 잘 보이도록 플레이어 마크를 하나 넣고

 

 

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

public class CamFollow : MonoBehaviour
{
    //CamPosition의 Transform 컴포넌트
    public Transform camTarget;
    void Update()
    {
        //카메라 위치를 타겟 위치에 일치시키기
        transform.position = camTarget.position;
    }
}

카메라가 플레이어를 잘 따라다닐 수 있도록 미니맵 카메라에도 스크립트를 넣는다.

 

 


 

 

플레이어 HP 추가

-PlayerMove 스크립트에서 설정한 후 EnemyFSM 스크립트에서 적용할 예정(PlayerMove에서 체력을 설정하는 이유는 체력 바로 표현할 예정이기 때문)

-플레이어 체력 변수, DamageAction 추가

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

public class PlayerMove : MonoBehaviour
{
    public float moveSpeed = 7f;
    //캐릭터 컨트롤러 컴포넌트 변수
    CharacterController cc;
    //중력 변수
    float gravity = -20f;
    //수직 속력 변수 초기화
    float yVelocity = 0;
    //점프력 변수
    public float jumpPower = 7f;
    //점프 상태 변수 처음엔 점프 안하므로 0
    int isJumping = 0;
    //플레이어 체력 변수
    public int hp = 100;

    private void Start()
    {
        //컴포넌트 변수는 Start 함수에서 할당을 해주어야 한다.
        cc = GetComponent<CharacterController>();
    }
    void Update()
    {
        //키보드 입력 받기
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        //이동 방향 설정
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        dir = dir.normalized;

        //바라보는 기준 방향으로 움직이기
        dir = Camera.main.transform.TransformDirection(dir);

        //캐릭터가 바닥에 착지 했다면 = 바닥면이 닿았다면 상태 초기화
        if(cc.collisionFlags == CollisionFlags.Below)
        {
            isJumping = 0;
            yVelocity = 0;
        }

        //점프 후 중력값 적용을 위해 중력 값 적용 위에 쓴다. Space 누르면 점프
        if (Input.GetButtonDown("Jump"))
        {
            if(isJumping == 0 || isJumping == 1)//만약 0또는 1인 상태이면 뛴다.
            {
                yVelocity = jumpPower;
                isJumping += 1;
            }
        }

        //수직 속도에 중력 값 적용
        yVelocity += gravity * Time.deltaTime;
        dir.y = yVelocity;

        //이동
        cc.Move(dir * moveSpeed * Time.deltaTime);
    }
    //플레이어 피격 함수
    public void DamageAction(int damage) //argument로 int 형의 damage를 받아 온다.
    {
        //적의 공력력 만큼 플레이어 체력 감소
        hp -= damage;
        //체력이 음수이면 0으로 초기화
        if(hp < 0)
        {
            hp = 0;
        }
        Debug.Log("플레이어 체력: " + hp);
    }
}

 

 

EnemyFSM 스크립트에서는 적 공격력 변수 설정, PlayerMove 스크립트에서 DamageAction 함수 가져오기

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    //적 공격력
    public int attackPower = 10;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
}

 

 


 

 

적이 특정 위치에서 멀어지면 다시 돌아가기

-Move() 함수에서 오리지널 좌표와 현재 좌표의 거리가 특정 거리보다 커지면 다시 오리지널 위치로 돌아가게 한다. Return 함수 생성

-오리지널 좌표는 Start 함수에 넣는다.

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    //적 공격력
    public int attackPower = 10;
    //적이 다시 되돌아갈 초기 위치 저장
    Vector3 originPos;
    //적 이동 가능 범위
    public float moveDistance = 20f;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
        //적의 초기 위치 저장
        originPos = transform.position;
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
            case EnemyState.Return:
                Return();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
        if(Vector3.Distance(transform.position, originPos) > moveDistance)
        {
            m_State = EnemyState.Return;
            Debug.Log("상태 전환");
        }
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        else if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
    void Return()
    {
        //오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
        if(Vector3.Distance(transform.position, originPos) > 0.1f)
        {
            //방향 구하기 (목적지 - 내 위치)
            Vector3 dir = (originPos - transform.position).normalized;
            //이동하기
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else  //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
        {
            transform.position = originPos;
            m_State = EnemyState.Idle;
            Debug.Log("상태 전환: Return -> Idle");
        }
    }
}

 

 


 

 

피격 상태 구현 -Coroutine 2초동안 기다려 같은 Delay

yield return이 적어도 1개는 있어야한다.

사용을 위해서는 코루틴 함수를 따로 만들어야 한다.

 

맞았을 때 피 이미지 등장 깜빡 2초 뒤에 Set.Active(false)로 꺼지게 한다.

 

EnemyFSM 스크립트에 구현한다.

PlayerFire 스크립트에서 EnemyFSM 적 체력에 접근하여 체력을 줄일 예정.

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    //적 공격력
    public int attackPower = 10;
    //적이 다시 되돌아갈 초기 위치 저장
    Vector3 originPos;
    //적 이동 가능 범위
    public float moveDistance = 20f;
    //적 체력 설정
    public int hp = 50;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
        //적의 초기 위치 저장
        originPos = transform.position;
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문 / Damaged와 Die는 AnyState이기 때문에 case에 들어가지 않는다.
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
            case EnemyState.Return:
                Return();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
        if(Vector3.Distance(transform.position, originPos) > moveDistance)
        {
            m_State = EnemyState.Return;
            Debug.Log("상태 전환");
        }
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        else if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
    void Return()
    {
        //오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
        if(Vector3.Distance(transform.position, originPos) > 0.1f)
        {
            //방향 구하기 (목적지 - 내 위치)
            Vector3 dir = (originPos - transform.position).normalized;
            //이동하기
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else  //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
        {
            transform.position = originPos;
            m_State = EnemyState.Idle;
            Debug.Log("상태 전환: Return -> Idle");
        }
    }
    //데미지 처리 Coroutine 함수
    IEnumerator DamageProcess()
    {
        //피격 모션만큼 기다림 애니메이션 고려
        yield return new WaitForSeconds(0.5f);
        //이동 상태 전환
        m_State = EnemyState.Move;
    }

    void Damaged()
    {
        //피격 코루틴 실행
        StartCoroutine(DamageProcess());
    }
    //데미지 실행 함수
    public void HitEnemy(int hitPower)
    {
        //플레이어의 공격력만큼 적 체력 감소
        hp -= hitPower;
    }
}

 

 

PlayerFire 스크립트에서 적 체력을 닳게 할 수 있다. (맞은 대상이 적이라면 체력을 깎아라 / Enemy Layer로 구분)

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

public class PlayerFire : MonoBehaviour
{
    //폭탄 발사 위치 정하기
    public GameObject firePosition;
    //폭탄 오브젝트 가져오기
    public GameObject bombs;
    //투척 파워
    public float throwPower = 10f;
    //폭탄 제한 시간
    float limitTime = 1f;
    //폭탄 현재 시간
    float currentTime = 0;
    //총알 이펙트
    public GameObject bulletEffect;
    //파티클 시스템 가져오기
    ParticleSystem ps;
    //총 공격력
    public int gunPower = 10;
    //총 불꽃 위치
    public GameObject gunFirePosition;
    //총 불꽃 이펙트
    public GameObject gunFires;

    private void Start()
    {
        //총알 이펙트의 파티클 시스템 가져오기
        ps = bulletEffect.GetComponent<ParticleSystem>();
    }

    void Update()
    {
        //마우스 오른쪽 버튼 입력
        if(Input.GetMouseButtonDown(1) && currentTime > limitTime)
        {

            //폭탄 생성
            GameObject bomb = Instantiate(bombs);
            //폭탄의 위치를 발사 위치로 이동
            bomb.transform.position = firePosition.transform.position;
            //폭탄의 Rigid Body 컴포넌트를 가져옴
            Rigidbody rb = bomb.GetComponent<Rigidbody>();
            //카메라의 정면으로 폭탄에 힘을 가해서 던지기/ForceMode는 질량과 힘을 모두 생각함
            rb.AddForce(Camera.main.transform.forward * throwPower, ForceMode.Impulse);
            //현재 시간 초기화
            currentTime = 0;
        }
        currentTime += Time.deltaTime;

        //마우스 왼쪽 버튼 입력
        if (Input.GetMouseButtonDown(0))
        {
            //Ray 생성 후 위치와 방향 설정
            Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
            //Ray가 부딪힌 대상 저장
            RaycastHit hitInfo = new RaycastHit();
            //Ray를 발사한 후 부딪힌 물체가 있으면 총알 이펙트 생성
            if(Physics.Raycast(ray, out hitInfo))
            {
                //만약 부딪힌 대상의 Layer가 Enemy라면
                if (hitInfo.transform.gameObject.layer == LayerMask.NameToLayer("Enemy"))
                {
                    //부딪힌 대상(=Enemy)의 EnemyFSM의 HitEnemy 함수를 실행
                    EnemyFSM eFSM = hitInfo.transform.GetComponent<EnemyFSM>();
                    eFSM.HitEnemy(gunPower);
                }
                bulletEffect.transform.position = hitInfo.point; //좌표 설정
                bulletEffect.transform.forward = hitInfo.normal; //총알 효과를 Ray가 부딪힌 지점의 법선 벡터와 일치하도록 설정
                ps.Play(); //파티클 플레이
            }
            //총구 화염
            GameObject gunFire = Instantiate(gunFires);
            gunFire.transform.position = gunFirePosition.transform.position;
            
        }
    }
}

 

 


 

 

 Die 함수 추가 -Coroutine 사용(적이 쓰러지자마자 없어지는 것 방지)

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

public class EnemyFSM : MonoBehaviour
{
    //적 상태 상수 6개 생성
    enum EnemyState
    {
        Idle,
        Move,
        Attack,
        Return,
        Damaged,
        Die
    }
    //적 상태 변수
    EnemyState m_State;
    //플레이어 발견 범위
    public float findDistance = 10f;
    //플레이어 Transform Component 적이 많이 생성되면 끌어 넣는게 비어있을 수 있다. Find로 찾아서 적용
    Transform player;
    //공격 범위
    public float attackDistance = 4f;
    //적 이동 속도
    public float moveSpeed = 5f;
    //적 캐릭터 컨트롤러 컴포넌트
    CharacterController cc;
    //적 공격 누적 시간
    float currentTime = 0;
    //적 공격 딜레이 시간
    float attackDelay = 2f;
    //적 공격력
    public int attackPower = 10;
    //적이 다시 되돌아갈 초기 위치 저장
    Vector3 originPos;
    //적 이동 가능 범위
    public float moveDistance = 20f;
    //적 체력 설정
    public int hp = 50;
    void Start()
    {
        //플레이어 Transform Component 할당
        player = GameObject.Find("Player").transform;
        //최초의 적 상태를 대기로 설정
        m_State = EnemyState.Idle;
        //적 캐릭터 컨트롤러 컴포넌트 할당
        cc = GetComponent<CharacterController>();
        //적의 초기 위치 저장
        originPos = transform.position;
    }
    void Update()
    {
        //적의 상태를 체크해서 상태별로 정해진 기능을 수행 Switch문 / Damaged와 Die는 AnyState이기 때문에 case에 들어가지 않는다.
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Move();
                break;
            case EnemyState.Attack:
                Attack();
                break;
            case EnemyState.Return:
                Return();
                break;
        }
    }
    void Idle()
    {
        //만약 플레이어와 적의 거리가 발견 범위 이내라면 Move 상태로 전환
        if(Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            Debug.Log("발견!");
        }
    }
    void Move()
    {
        //만약 현재 위치와 초기 위치 거리가 이동 가능 범위보다 크면 복귀
        if(Vector3.Distance(transform.position, originPos) > moveDistance)
        {
            m_State = EnemyState.Return;
            Debug.Log("상태 전환");
        }
        //만약 플레이어와 적의 거리가 공격 범위보다 크다면 플레이어를 향해 이동
        else if (Vector3.Distance(transform.position, player.position) > attackDistance)
        {
            //이동 방향
            Vector3 dir = (player.position - transform.position).normalized;
            //이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else
        {
            m_State = EnemyState.Attack;
            Debug.Log("공격!");
            //공격이 바로 안되므로 공격 딜레이 시간 만큼 미리 진행을 시켜놓아야 한다.
            currentTime = attackDelay;
        }
    }
    void Attack()
    {
        //플레이어와 적의 거리가 공격 범위 이내라면 공격
        if(Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            //일정 시간마다 플레이어 공격
            currentTime += Time.deltaTime;
            if(currentTime > attackDelay)
            {
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("공격!");
                currentTime = 0;
            }
        }
        //그렇지 않다면, Move();
        else
        {
            m_State = EnemyState.Move;
            Debug.Log("이동");
            currentTime = 0;
        }
    }
    void Return()
    {
        //오리지널 방향으로 돌아가기 특정 좌표값보다 범위로 나타내는게 에러가 덜 날 가능성이 높다.
        if(Vector3.Distance(transform.position, originPos) > 0.1f)
        {
            //방향 구하기 (목적지 - 내 위치)
            Vector3 dir = (originPos - transform.position).normalized;
            //이동하기
            cc.Move(dir * moveSpeed * Time.deltaTime);
        }
        else  //그렇지 않다면 적 위치를 초기 위치로 조정하고, 대기 상태로 전환한다.
        {
            transform.position = originPos;
            m_State = EnemyState.Idle;
            Debug.Log("상태 전환: Return -> Idle");
        }
    }
    //데미지 처리 Coroutine 함수
    IEnumerator DamageProcess()
    {
        //피격 모션만큼 기다림 애니메이션 고려
        yield return new WaitForSeconds(0.5f);
        //이동 상태 전환
        m_State = EnemyState.Move;
    }

    void Damaged()
    {
        //피격 코루틴 실행
        StartCoroutine(DamageProcess());
    }
    //데미지 실행 함수
    public void HitEnemy(int hitPower)
    {
        //적이 피격 상태이면 플레이어를 때릴 수 없게
        if(m_State == EnemyState.Damaged)
        {
            return;
        }
        //플레이어의 공격력만큼 적 체력 감소
        hp -= hitPower;
        //적 체력이 0보다 크면 피격
        if(hp > 0)
        {
            m_State = EnemyState.Damaged;
            Damaged();  //코루틴 호출
            Debug.Log("적 체력 표시: " + hp);
        }
        //0보다 작으면 죽음
        else
        {
            m_State = EnemyState.Die;
            Debug.Log("죽음");
            Die();
        }
        IEnumerator DieProcess()
        {
            //적 캐릭터 컨트롤러 컴포넌트 비활성화
            cc.enabled = false;
            //2초 대기
            yield return new WaitForSeconds(3f);
            //적 제거
            Destroy(gameObject);
        }
        void Die()
        {
            //실행 중인 이전 Coroutine 중지
            StopAllCoroutines();
            //죽음 Coroutine 실행
            StartCoroutine(DieProcess());
        }
    }
}

 

 

728x90
반응형