본문 바로가기

3D_FPS

08_05 Enemy_(Move,Attack,Die)

목차

 

 

 

 

(참고) 고스톱에서 유용하게 쓰이는 FSM

더보기
더보기
고스톱에서 유용하게 쓰이는 FSM의 모습

우리수업에서의 목적 = 목차,


Move()  상태 구현하기

=> 타겟쪽으로 이동하기

 

 

//타겟쪽으로 이동하고싶다 => 타겟은 Player

 

 

 //타겟쪽으로 이동하고 싶다.
    // 필요속성 : 이동속도, 타겟, CharactoerController 

    public float speed = 5;
    public Transform target;
    CharacterController cc;

변수 선언

 private void Move()
    {
        //타겟쪽으로 이동하고싶다.
        //1. 방향이 필요
        // -> Target - me
        Vector3 dir = target.position - transform.position;
        dir.Normalize();

        // 2. 이동하고싶다
        cc.Move(dir * speed * Time.deltaTime);
    }

 

Move() 함수에 들어가는 코드

 

 


(참고) 가독성 좋게 코드를 정리할 수 있는 Unity 의 기능!!

더보기
더보기

 

변수선언 (속성) 을 그냥 함수위에다가 놔도 상관은 없음!

근데 속성을 관리차원에서 위에 쭉 나열 하는것임!

 

근데 속성이 많아지면,,?

 

관리 차원에서 Unity 에서 제공하는 기능이 있음

#region , #endregion 으로 정리한 모습
접은 모습

 


Enemy.cs 에 CharacterController 속성추가

 

Enemy Inspecter 창


그렇다면 

 

Enemy 가 Player 를 보면서 움직이려면??

즉, Player 쪽으로 가는 방향 dir 이 앞방향으로 하게 하려면??

 

 

앞방향이 dir 이 되게 한 모습

나의 forward 가 dir 방향이 되면 됨!

 

근데 이렇게 되면? 플레이어쪽으로 뒤돌아볼 때, 너무 확확 뒤돌아봄!

여기서

 

요렇게

확! 뒤돌아봄!

 

이걸 좀 고치고싶어서,, 부드럽게 해보고 싶다!


부드럽게 진행 해보기

 

Lerp 사용!

Lerp 를 사용한 모습

Lerp(     from      ,        to      ,       %    )

 

Vector3. Lerp (   이 벡터부터 , 이 벡터까지  ,   %  )

 


 

이제는 뒤돌아 볼 때, 부드럽게 돌아본다!

 

그런데 여기서 문제,

 

 

z값이 같으면 ( 즉, player 와 나란히 서있으면) 덤블링해서 Enemy 가 옴...!? 

이것도 멋있긴 하지만,, 우리의 계획과는 좀 다르다..!

 

이건 EulerAngle 의 오류 때문임!

 

(참고)EulerAngle 의 오류

더보기
더보기

순서에 따라 결과 값이 달라지기 때문임!

 

Ex) 

1. X 축으로 90도 회전 => Z축으로 90도 회전 

2. Z 축으로 90도 회전 => X축으로 90도 회전 

 

 이건 언뜻보면 같을것 같지만 돌려보면 결과 값이 다르다! 

이런 문제를 해결하기 위해

 

우리는 축하나만 꽂아놓고 돌리는 Quaternion (사원수) 로 써보자!

 

 

//타겟쪽으로 회전하고 싶다.
        transform.rotation = Quaternion.Lerp( transform.rotation, Quaternion.LookRotation(dir), 5 * Time.deltaTime);

 

Quaternion.Lerp( 지금 방향,   (쿼터니언으로)바라볼 방향  , t )

 

 

(참고) 왜 transform.rotation ?

더보기
더보기

쿼터니온을 쓸때는,

transform.eulerAngle 이 아닌,

transform.rotation 을 쓴다.

 

eulerAngle 은 => Vector 3 임!

 

 

 


여기서 또  문제.

 

Player가 공중에 있으면 떠오르면서 날라오는 모습

Enemy 가 떠오르면 날아서 옴,,?

우리는 중력을 넣어줘야 한다!

 

여기서,

 

cc 기능중에 SimpleMove 라고 있음. 이걸 쓰면 중력을 받는모습을 볼 수 잇음!

 

이제는 밑에서 오는 모습

(참고) SimpleMove 의 한계

더보기
더보기

RigidBody 의 중력값을 제어하기 힘든것 처럼, 이것도 우리가 중력값을 제어하기는 힘들다고 한다!

즉, 다이나믹한걸 원한다면 이건 좀 힘들 수도 있다고 함!


근데 여기서 또 문제.. 

Enemy  가 앞을  보는게 아니라, Player 를 우러러본다..ㅋㅋ

 

이걸 해결해 보자.

 

문제는, Enemy 의 방향이다. Enemy 의 dir 의 Y방향만 그냥 고정시켜주면 되지 않을까!!

 

앞을 보는 방향  == y 값이 0 이라고 볼 수 있다!

이렇게 넣어주면?
이제는 우리의 계획대로 오는 모습

 

 

여기까지의 Move() 코드

 

private void Move()
    {
        //타겟쪽으로 이동하고싶다.
        //1. 방향이 필요
        // -> Target - me
        Vector3 dir = target.position - transform.position;

        // y 부분은 회전하지 않게 하고싶다.
        dir.y = 0;

        dir.Normalize();

        //타겟쪽으로 회전하고 싶다.
        transform.rotation = Quaternion.Lerp( transform.rotation, Quaternion.LookRotation(dir), 5 * Time.deltaTime);

        // 2. 이동하고싶다
        //cc.Move(dir * speed * Time.deltaTime);

        cc.SimpleMove(dir * speed); // SimpleMove 의 중력값을 조정을 못하니까, 다이나믹한건 못함

    }

 

 


    이제는, 공격범위에 타겟이 들어오면 상태를 공격으로 전환하고 싶다.

변수 선언한 모습

 

크기가 1이 되면 안되니까 normalize 되기 전에 써야 함!

 

 

 

 

 

 


참고) 

기획자가 distance 가 2 라고 는 했는데 거리를 잘 모를 수 있다

 잘 가늠이 안될 때 가시적으로 나타내는 방법!!

 

OnDrawGizmos() 를 사용하면 됨!

 

빨간색으로, 구를 그리게 됨!

 

홀리찟


Attack() 상태 구현

 

일정시간에 한번씩 공격하게 하고싶다!

 

    // 일정시간에 한번씩 공격하고 싶다.
    // 필요속성 : 공격대기시간
    public float attackDelayTime = 2;

    private void Attack()
    {
        currentTime += Time.deltaTime;
        if (currentTime > attackDelayTime)
        {
            currentTime = 0;
            print("attack!!");
        }

    }

 


이제 Attack 했다가 타겟이 공격범위를 벗어난다면? 상태를 '이동' 으로 전환하자!

 

 

(참고)인스펙터 멀티로 사용하는 법

더보기
더보기

Player 누르고 Alt + P 를 누르면 Player 의 인스펙터창이 하나 더뜸!!!


어떤 상태에서든 당하는 피격!!

피격을 당해야 데미지 상태를 줄 수 있음!!

부딪힌 이벤트 발생이 먼저 이기 때문에

 

 

이 기준은 HP

 

 

 

이건 어디서 당할까? Player 가 총쏴서 맞을때임!!

 

그러면 Player.cs 로 가보자!

 

쐈는데 안경맞으면 안죽네유.. ㅋㅋ

 

안경 콜라이더 빼자!


조준점(Cross Hair) 만들기!

 

이미지 생성

(참고) world Space

더보기
더보기
이건 월드상에서 쓸수 있는것!

여기서 나오는 X,Y 값은 더이상 Pixel 이 아님!

 

width, Height 를 10으로 맞춤!
앵커 프리셋 Alt 누르고 중간에 있는것 클릭

 


OnDamageProcess  이벤트 구현 ( 피격당했을 시 )

HP 깎아 주기

 

// 3대 맞으면 죽도록 처리해보자!

 

위에서 public int hp = 3; 선언 해줌

3대 맞으면 죽게 설정

 

(참고) 게임잼을 대하는 태도

더보기
더보기

재밌게 하면 됨. 얘가 싫고 어쩌고 이런거 말고 맞춰가는 것에 초점을 맞춰서 재밋게 해보자

 


피격상태 구현하기! 

(피격당하면 일정시간 기다렸다가 상태를  Idle 로 전환하고 싶다.)

 

피격상태를 구현해 볼거임

 

 

 

(참고)FPS 에서 맞은 위치에 따라서 피가 닳는게 다르게 하는것

더보기
더보기

부위별로 콜라이더를 넣어줘서, 다른 함수가 나오게 하는것임!

 

Ray 를 머리에 쐈는데, transform 이라고 하면 머리가 아니라, 그냥enemy 라고 나올 수도 있음! 

그러니까 transform 이라고 하지말고 collider 라고 넣어야 함!

 


Die() 상태 구현하기

 

죽게 되면, 아래로 쭉 내려가게 되면서

밑에 다 내려가면 Destroy 시킬 예정

 

 

 

떨어지는 스피드 선언

    private void Die()
    {
        //아래로 사라지도록 하자.
        transform.position += Vector3.down * dieSpeed * Time.deltaTime;

        // 완전히 사라지면 제거
        if (transform.position.y < -3)
        {
            Destroy(gameObject);     
        }

    }

 

 

 


만약, 

    private void Attack()
    {
        currentTime += Time.deltaTime;
        if (currentTime > attackDelayTime)
        {
            currentTime = 0;
            print("attack!!");
        }
        //타겟이 공격범위를 벗어나면 상태를 이동으로 전환하고 싶다.
        float distance = Vector3.Distance(target.position, transform.position);

        if(distance > attackRange)
        {
            m_State = EnemyState.Move;    
        }
    }

이 Attack() 코드에서 currentTime 이 한 1.9 초 정도 흘러서 실행되기 직전이었음.

 

근데 이때 피격당해서 Damage() 로 옮겨졌음

 

그러면

 

    private void Damage()
    {
        //여기서는 Update 로 돌아감!


        // 넉백 포지션
        transform.position = Vector3.Lerp(transform.position, knockBackpos, 10 * Time.deltaTime);

        currentTime += Time.deltaTime;
        if (currentTime > damageDelayTime)
        {
            currentTime = 0;
            m_State = EnemyState.Idle;
        }
    }

여기서 , 0.1 초만에 Idle 로 가야하게 됨...

그러니까 피격 당했을 때, currentTime 을 0 으로 초기화 시켜주자!

 

초기화 해줘야 함

 


죽었는데 계속 맞게 하면 안되니까, 충돌체를 꺼버리자.

 


넉백 구현( 1. 그냥 위치이동, 2. Lerp 로 자연스럽게 이동 )

1. 그냥 위치이동

 

스피드와 넉벡 벡터선언

 

여기 shootDirection(총알 방향) 은 Ray에서 쉽게 설정 할 수 있다!

 

 

 shootDirection 까지 설정해서 OnDamageProcess 에서 불러준 부분 (PlayerFire.cs 에 Shoot Ray() 부분 )

// Ray 를 발사해서 만약 부딪혔다면
            if (Physics.Raycast(ray, out hitInfo,1000, ~layer ))
            {
                // 부딛힌 지점에 파편 튀게 하고싶다.  

                //1. 부딪힌 지점으로 이동시키기
                bulletImpact.position = hitInfo.point;

                // 2. 파편효과 재생하기

                //파편이 튀는 방향을 부딪힌 지점이 향하는 방향과 일치시켜주자.
                bulletImpact.forward = hitInfo.normal;

                // 재생할 수도 있으니까 Stop 해주고 
                bulletPS.Stop();

                // Play 해주자
                bulletPS.Play();


                // 부딪힌 녀석이 Enemy 라면 피격 이벤트 호출하자
                // Layer, tag 이런것도 되지만
                //컴포넌트 갖고있는지 체크해도 됨!
                Enemy enemy = hitInfo.transform.GetComponent<Enemy>();

                if (enemy != null)
                {
                    enemy.OnDamageProcess(ray.direction);
                
                }

            }

 

 

 

Enemy.cs 에서 transform.position 설정

 

 

 

2. Lerp로 구현하기

넉벡포스 = 지금위치 + 총알방향 * 스피드

 

우선 넉벡 포스를 설정해둔다

이 부분에 Time.deltaTime 이 들어가지 않는 이유 = update() 가 아니라서 딱 한프레임 만 들어가기 때문.

 

 

Damage() 함수에서 계속 돌아감

 

 

문제: 땅밑으로 내려간다!

땅 밑으로 내려가기 때문에, shootDirection 의 y값을 0으로 만들자!

shootDirection 의 y값을 0 으로 만들 준 상태

 

이제는 땅으로 내려가지 않음!


Lerp에 관련된 이야기

Linear Interpolation

선형보간 이라고 함

 

보간 한다는것

빨간색을 파란색으로 만드는 과정

 

선형
0.5 씩 보간 됨! => Lerp

 

근데 여기서 재밌는게 우리가 정한 시간동안(2초라고 치자)

0 에서 1까지 가게 처리를 할 수가 있음

 

 

 

이렇게 하면, 2초가 걸리게 할 수 있음!!! currentTime /2 => 1 이되면 100% 가 되는 거임!

 

 

 

그래픽에서 a 값

 

 

easing in, easing out 이라고 함

itween  이라고 함 이걸

 

이런 움직임을 만드려면 베이지어 곡선을 이용해야함!

 

 

 

Lerp 의 식 => 

 

 1a*T + ( 1- a )*S