반응형

플레이어가 공격하였을 때 몬스터에게 충격을 가하는 이팩트를 만들겠습니다

 

Player를 선택하고 Animation을 들어갑니다

 

 

Player의 animation의 Hikkick을 선택합니다

이것은 Player가 공격할 때 취하는 애니메이션입니다

지금 이것이 Read-Only로 읽기 전용으로 되어 있습니다

이것을 바꾸어야 합니다 

 

 

 

 

 

 

 

 

Hikkick 애니메이션을 찾아 Ctrl +D를 눌러서 복사합니다

 

복사본 이름을 Hikick_new로 바꾸고  Animator에 들어가서 attack에 복사본을  motion에 드래그해서 바꾸어 놓습니다

 

PlayerFSM을 선택하고 수정합니다

 

 

 

 

 

 

 

PlayerFSM 함수 추가


public class PlayerFSM : MonoBehaviour
{    
    public enum State
    {
        Idle,
        Move,
        Attack,
        AttackWait,
        Dead
    }
    //idle 상태를 기본 상태로 지정
    public State currentState = State.Idle;

    //마우수 클릭 지점, 플레이어가 이동할 목적지의 좌표를 저장할 예정
    Vector3 curTargetPos;

    GameObject curEnemy;

    public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다

    public float moveSpeed = 2f; //초당 2미터의 속도로 이동

    float attackDelay = 2f; // 공격을 한번 하고 다시 공격할 때까지의 지연시간

    float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수

    float attackDistance = 1.5f; // 공격 거리 (적과의 거리)

    float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작하기 위한 거리

    PlayerAni myAni;
    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
    }


    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }
        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();
    }

    // 적을 공격하기 위한 함수 
    public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }
        curEnemy = enemy;
        curTargetPos = curEnemy.transform.position;

        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }
    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }
        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                IdleState();
                break;
            case State.Move:
                MoveState();
                break;
            case State.Attack:
                AttackState();
                break;
            case State.AttackWait:
                AttackWaitState();
                break;
            case State.Dead:
                DeadState();
                break;
            default:
                break;
        }
    }
   void IdleState()
    {
    }
    void MoveState()
    {
        TurnToDestination();
        MoveToDestination();
    }
    void AttackState()
    {
        attackTimer = 0f;

        //transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전시키는 함수
        transform.LookAt(curTargetPos);
        ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE);
    }
    void AttackWaitState()
    {
        if (attackTimer > attackDelay)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);

        }

        attackTimer += Time.deltaTime;
    }
    void DeadState()
    {

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        curEnemy = null;
        curTargetPos = tPos;
        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }

    void TurnToDestination()
    {
        // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함
        Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);

        //Quaternion.RotateTowards(현재의 rotation값, 최종 목표 rotation 값, 최대 회전각)
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        //Vector3.MoveTowards(시작 지점, 목표지점, 최대 이동거리)
        transform.position = Vector3.MoveTowards(transform.position, curTargetPos,moveSpeed * Time.deltaTime);

        if (curEnemy == null)
        {
            //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
            if (transform.position == curTargetPos)
            {
                ChangeState(State.Idle, PlayerAni.ANI_IDLE);
            }
        }
        else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

 

 

 

 

 

 

그리고 Animation을 활성화시켜서 Player 공격하는 시점 프레임에 Add Event를 추가시켜 AttackCalculate() 함수를 등록시킵니다.

 

 

Asset Store에 들어가서 Simple Partice Pack을 다운로드합니다

 

Simpe Particle Pack에서 Explosions에 Burst를 임포트 합니다

 

 

 

 

 

 

 

 

 

Enemy 오브젝트를 선택하고 자식으로 빈 오브젝트를 만들고 이름을 EffectPos 라 하고  임포트 한 Burst 프리 팹을 찾아 드래그해서 자식으로 놓습니다 

그리고 Enemy 오브젝트에 머리 위로 위치시켜 이팩트가 터지면 잘 보이도록 위치시킵니다 

 

EnemyFSM 스크립트를 선택하고 수정합니다

 

 

 

 

 

 

 

 

 



public class EnemyFSM : MonoBehaviour
{
    public enum State
    {
        Idle,  //정지
        Chase,  //추적
        Attack,  //공격
        Dead,   //사망
        NoState  //아무 일도 없는 상태
    }

    public State currentState = State.Idle;

    EnemyAni myAni;

    Transform player;

    float chaseDistance = 5f; // 플레이어를 향해 몬스터가 추적을 시작할 거리
    float attackDistance = 2.5f; // 플레이어가 안쪽으로 들오오 게 되면 공격을 시작
    float reChaseDistance = 3f; // 플레이어가 도망갈 경우 얼마나 떨어져야 다시 추적

    float rotAnglePerSecond = 360f; // 초당 회전 각도
    float moveSpeed = 1.3f; // 몬스터의 이동 속도

    float attackDelay = 2f;
    float attackTimer = 0f;

    public ParticleSystem hitEffect;

    void Start()
    {
        myAni = GetComponent<EnemyAni>();
        ChangeState(State.Idle, EnemyAni.IDLE);

        player = GameObject.FindGameObjectWithTag("Player").transform;

        hitEffect.Stop();
    }

    public void ShowHitEffect()
    {
        hitEffect.Play();
    }

    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                IdleState();
                break;
            case State.Chase:
                ChaseState();
                break;
            case State.Attack:
                AttackState();
                break;
            case State.Dead:
                DeadState();
                break;
            case State.NoState:
                NoState();
                break;          
        }
    }

 

 


    public void ChangeState(State newState, string aniName)
    {
        if (currentState == newState)
        {
            return;
        }

        currentState = newState;
        myAni.ChangeAni(aniName);
    }
    void IdleState()
    {
        if (GetDistanceFromPlayer() < chaseDistance)
        {
            ChangeState(State.Chase, EnemyAni.WALK);
        }
    }

    void ChaseState()
    {
        //몬스터가 공격 가능 거리 안으로 들어가면 공격 상태
        if (GetDistanceFromPlayer() < attackDistance)
        {
            ChangeState(State.Attack, EnemyAni.ATTACK);
        }
        else
        {
            TurnToDestination();
            MoveToDestination();
        }   
    }

    void AttackState()
    {
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;
            }

            attackTimer += Time.deltaTime;
        }
    }
    void DeadState()
    {

    }
    void NoState()
    {

    }

    void TurnToDestination()
    {
        Quaternion lookRotation = Quaternion.LookRotation(player.position - transform.position);

        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        transform.position = Vector3.MoveTowards(transform.position, player.position, moveSpeed * Time.deltaTime);
    }

    //플레이어와 거리을 재는 함수
    float GetDistanceFromPlayer()
    {
        float distance = Vector3.Distance(transform.position, player.position);
        return distance;
    }

    // Update is called once per frame
    void Update()
    {
        UpdateState();
    }
}

 

 

 

Enemy를 선택하고 자식으로 두었던 Burst 이팩트를 수정한 스크립트에 등록합니다

 

Burst 이팩트를 활성화시킵니다

 

 

 

 

게임을 실행시켜 플레이어를 적에게 공격하면 이팩트가 잘 터지는지 확인합니다

 

https://youtu.be/y79imRjp1rM

 

반응형

+ Recent posts