본문 바로가기
etc./🕹️unity

3D FPS

by yewoneeee 2022. 6. 11.

애니메이션 설정

# 실습 영상

조준점이 없어서 맞추기가 어려운데ㅋㅋ

적 애니메이션, 총 발사, 맵 이동, 카메라 이동 등을 위주로 공부했음

# CameraRotate.cs

플레이어 마우스에 따라 카메라 회전

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 마우스의 입력값을 이용해서 회전하고 싶다.
public class CameraRotate : MonoBehaviour
{
    float rx;
    float ry;
    public float rotSpeed = 200;

    public Transform body;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // 1. 마우스의 입력값을 이용해서
        float mx=Input.GetAxis("Mouse X");
        float my = Input.GetAxis("Mouse Y");
        //  deltatime은 transform에 있는 정보를 변경할땐 무조건 적어야함
        // 근데 deltaTime은 대략 60분의 1초로 mx, my값을 대략 60분의 1로 감소시키기 때문에 증폭시켜 줄 필요가 있음
        rx += rotSpeed * my * Time.deltaTime; //y축 회전이 x 방향과 관련
        ry += rotSpeed * mx * Time.deltaTime; //x축 회전이 y 방향과 관련
        //print(mx+","+my);

        // rx의 회전 값을 제한하고 싶다(마우스의 위아래 각도를 제한한다) -> 고개를 위아래로 360도 회전이 불가능하기 때문
        // +80도 -80도 사이로 제한
        rx = Mathf.Clamp(rx, -80, 80);

        // 2. 회전하고 싶다
        transform.eulerAngles = new Vector3(-rx, ry, 0); //마우스를 위로하면 아래를 보고, 아래로하면 위를 보게 해야하기 때문에 rx에 -1곱해줌
        body.transform.eulerAngles = new Vector3(0, ry, 0);
    }
}

 

# AgentTest.cs

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


public class AgentTest : MonoBehaviour
{
    NavMeshAgent agent;
    public Transform target; // 목적지
    // Start is called before the first frame update
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        
    }

    // Update is called once per frame
    void Update()
    {
        agent.destination = target.position;
    }
}

 

# Enemy.cs

플레이어와의 거리에 따라 적의 행동 변경

플레이어가 적의 공격 범위안에 들어오면 플레이어를 공격하도록 설정

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

// FSM(상태머신)
// FSM으로 상태를 제어하고 싶다.
// 정지, 이동, 공격, 죽음
public class Enemy : MonoBehaviour
{
    NavMeshAgent agent;
    public enum State
    {
        Idle,
        Move,
        Attack,
        Die,
    }


    public State state;

    //Animator>Animator Controller>Animation Clips
    public Animator anim;

    PlayerHP php;
    // Start is called before the first frame update
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        state = State.Idle;

        target = GameObject.Find("Player");
        php = target.GetComponent<PlayerHP>();
    }

    // Update is called once per frame
    void Update()
    {
        if (state == State.Idle)
        {
            UpdateIdle();
        }
        else if (state == State.Move)
        {
            UpdateMove();
        }
    }
    GameObject target;
    public float findDistance = 5;
    private void UpdateIdle()
    {
        // target이 감지거리 안에 들어오면 Move로 전이하고 싶다.
        // 1. 나와 target의 거리를 구해서
        float distance = Vector3.Distance(transform.position, target.transform.position);
        // 2. 만약 그 거리가 감지거리보다 작으면
        if (distance < findDistance)
        {
            // 3. Move상태로 전이하고 싶다.
            state = State.Move;
            anim.SetTrigger("Move");
            agent.destination = target.transform.position;
        }
    }
    public float speed = 1;
    public float attackDistance = 1.5f;
    private void UpdateMove()
    {
        // target방향으로 이동하다가 target이 공격거리 안에 들어오면 Attack으로 전이하고 싶다.
        // 1. target 방향으로 이동하고 싶다. P=P0+vt
        /*
        Vector3 dir = target.transform.position - transform.position;
        dir.Normalize();
        transform.position += dir * Time.deltaTime * speed;
        */
        agent.destination = target.transform.position;
        // 2. 나와 target의 거리를 구해서
        float distance = Vector3.Distance(transform.position, target.transform.position);
        // 3. 만약 그 거리가 공격거리보다 작으면
        if (distance < attackDistance)
        {
            // 4. Attack 상태로 전이하고 싶다.
            state = State.Attack;
            anim.SetTrigger("Attack");
            agent.isStopped = true; //멈춤
        }
    }

    internal void OnEventAttack() // internal은 같은 프로그램 내에서만 public
    {
        // 공격 행위
        // 5. 나와 target의 거리를 구해서
        float distance = Vector3.Distance(transform.position, target.transform.position);
        // 6. 그 거리가 공격거리보다 크다면
        if (distance > attackDistance) // 공격 실패
        {
            // 7. Move상태로 전이하고 싶다.
            state = State.Move;
            anim.SetTrigger("Move");
            agent.isStopped = false;
        }
        else // 공격 성공
        {
            // 4. 플레이어를 공격하고
            php.AddDamage();
            // HitManager의 Hit함수를 호출 하고 싶다.
            HitManager.instance.Hit();
        }
    }

    float currentTime;
    float attackTime = 1;
    private void UpdateAttack()
    {
        // 일정시간마다 공격을 하되 공격시점에 target이 공격거리 밖에 있으면 Move상태로 전이하고 싶다. 그렇지 않으면 계속 반복해서 공격!
        // 1. 시간이 흐르다가
        currentTime += Time.deltaTime; //계속계속 더해져서 currentTime이 1이 되면 1초가 흐른것
        // 2. 만약 현재시간이 공격시간이 되면
        if (currentTime > attackTime)
        {
            // 3. 현재시간을 초기화하고
            currentTime = 0;
            // 5. 나와 target의 거리를 구해서
            float distance = Vector3.Distance(transform.position, target.transform.position);
            // 6. 그 거리가 공격거리보다 크다면
            if (distance > attackDistance) // 공격 실패
            {
                // 7. Move상태로 전이하고 싶다.
                state = State.Move;
                agent.isStopped = false;
            }
            else // 공격 성공
            {
                // 4. 플레이어를 공격하고
                php.AddDamage();
                // HitManager의 Hit함수를 호출 하고 싶다.
                HitManager.instance.Hit();
            }
        }
    }
    public void AddDamage(int damage)
    {
        agent.isStopped = true; //정지
        state = State.Die;
        anim.SetTrigger("Die");
        Destroy(gameObject, 3); //3초후 파괴
    } //destroy함수는 즉각 실행이 아니고 요청하는 것이기 때문에
    // 요청하면 자기자신의 Ondestroy가 실행
    private void OnDestroy()
    {
        EnemyManager.instance.COUNT--;
    }
}

 

# EnemyAnimationEvent.cs

적 애니메이션 동작 설정

이벤트 발생 시 컴포넌트에 알려주는 역할

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

// 애니메이션 공격 동작에 호출할 이벤트 함수를 제작하고 싶다.
// 이벤트가 발생하면 Enemy컴포넌트에게 알려주고 싶다.
public class EnemyAnimationEvent : MonoBehaviour
{
    public Enemy enemy;
    public void OnEventAttack()
    {
        //print("onEventAttack");
        enemy.OnEventAttack(); // 비주얼스튜디오에서 자동 함수생성-> ctrl+. 또는 alt+enter
                                // 함수를 찾을려면 OnEventAttack에 포커스하고 f12누르면 자동으로 찾아줌
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

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

 

# EnemyManager.cs

일정 시간마다 적 스폰

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

// 일정 시간마다 적공장에서 적을 생성해서 내 위치에 가져다 놓고 싶다.
public class EnemyManager : MonoBehaviour
{
    public static EnemyManager instance;
    private void Awake()
    {
        instance = this;
    }

    public GameObject enemyFactory;
    public float createTime = 2;

    int count;
    public int COUNT
    {
        get { return count; }
        set { count = value;
            if (count < 0)
            {
                count = 0;
            }
            if (count > maxCount)
            {
                count = maxCount;
            }
        }
    }
    public int maxCount = 2;

    // Start is called before the first frame update
    IEnumerator Start()
    {
        while (true)
        {
            if (count < maxCount)
            {
                // 1. 적 공장에서 적을 생성하고
                GameObject enemy = Instantiate(enemyFactory);

                // 2. 내 위치에 가져다 놓고 싶다. 
                enemy.transform.position = this.transform.position + new Vector3(Random.value * 2, 0, Random.value * 2);
                // 3. 내 방향과 일치시키고 싶다. 
                enemy.transform.forward = transform.forward; //forward 대신 right, rotation도 가능
                count++;
            }

            // 4. 생성시간동안 대기하고 싶다.
            yield return new WaitForSeconds(createTime);
        }
    }

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

 

# Gun.cs

Ray 사용해서 바라보고 있는 곳에 마우스로 총 쏘기

벡터를 사용해서 총알 자국 남기기

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

// Ray를 이용해서 바라보고 닿은 곳에 총을 쏘고 싶다.
// 총알 자국을 남기고 싶다.
public class Gun : MonoBehaviour
{
    public ParticleSystem bulletImpact; //재생하기 위해 gameObject가 아니라 particleSystem사용
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //만약 마우스 왼쪽버튼을 누르면
        if (Input.GetButtonDown("Fire1"))
        {
            // 1. 시선을 만들고
            Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward); //원점과 방향을 넣어줘야함
                                                                                              // 2. 그 시선을 이용해서 바라봤는데 만약 닿은 곳이 있다면?
            RaycastHit hitInfo;
            if (Physics.Raycast(ray, out hitInfo))
            {
                // 3. 닿은 곳에 총알자국을 가져다 놓고 싶다.
                bulletImpact.transform.position = hitInfo.point; //hitInfo의 transform.position은 그 물체의 정중앙을 말함(속) 따라서 우리가 보는 겉면에 표시할려면 point사용
                // 4. 총알 자국 VFX를 재생하고 싶다 (우리가 factory처럼 만드는게 아니라서 재생시켜줘야함)
                bulletImpact.Stop();
                bulletImpact.Play();
                // 5. 총알 자국의 방향을 닿은 곳의 Normal방향으로 회전하고 싶다.(법선벡터)
                // =총알 자국의 forward방향(총알이 forward방향(파란색화살표)으로 튀기 때문)과 닿은 곳의 Normal방향을 일치시키고 싶다.
                bulletImpact.transform.forward = hitInfo.normal;

                // 만약 hitInfo가 Enemy컴포넌트를 가지고 있다면?
                Enemy enemy = hitInfo.transform.GetComponent<Enemy>();
                if (enemy != null)
                {
                    // enemy의 AddDamage함수를 호출하고 싶다.
                    enemy.AddDamage(1);
                }
            }
        }
    }
}

 

# HitManager.cs

코루틴을 사용해서 적이 플레이어 공격했을 때 공격받았다는 이미지 보여주기

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

public class HitManager : MonoBehaviour
{
    public static HitManager instance;
    private void Awake()
    {
        instance = this;
    }
    public GameObject imageHit;
    // Start is called before the first frame update
    void Start()
    {
        // 태어날 때 imageHit를 보이지 않게 하고 싶다.
        imageHit.SetActive(false);
    }
    public void Hit()
    {
        // 깜빡거리고 싶다.
        // 1. imageHit를 보이게 하고 싶다. 2. 0.1초 후에 3. imageHit를 안보이게 하고 싶다. -> 함수는 한꺼번에 실행되고 끝나기 때문에 1번이 동작하지 않는 것처럼 보일 수 있음
        // 따라서 코루틴(Coroutine)함수 사용 -> 동시에 실행되는 함수 -> 여러개의 진입점을 가짐
        StartCoroutine("IeHit"); // StartCoroutine(leHit());도 가능
    }

    IEnumerator IeHit()
    {
        // 1. imageHit를 보이게 하고 싶다.
        imageHit.SetActive(true);
        // 2. 0.1초 후에 
        yield return new WaitForSeconds(0.1f);
        // 3. imageHit를 안보이게 하고 싶다.
        imageHit.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        // 적이 플레이어를 공격했을 때 imageHit를 깜빡이게 하고 싶다.
        if (Input.GetButtonDown("Jump"))
        {
            Hit();
        }
    }
}

 

# PlayerHP.cs

플레이어 HP 초기화

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

// 태어날 때 체력을 최대 체력으로 하고 싶다.
// 적이 플레이어를 공격할 때 체력을 감소하고 싶다.
// 체력이 변경되면 UI에도 반영하고 싶다.

public class PlayerHP : MonoBehaviour
{
    int hp;
    public int maxHP=3;
    public Slider sliderHP;

    //property 는 변수처럼 사용할 수 있는 함수
    public int HP
    {
        get { return hp; }
        set
        {
            hp = value;
            sliderHP.value = value;
        }
    }
    public void AddDamage()
    {
        HP -= 1; // get, set함수가 불림
    }

    // Start is called before the first frame update
    void Start()
    {
        sliderHP.maxValue = maxHP;
        HP = maxHP;
    }

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

 

# PlayerMove.cs

사용자의 입력에 따른 이동

중력을 반영해서 속도 계산

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

// 사용자의 입력에 따라 앞뒤좌우로 이동하고 싶다.
// 점프를 뛰고 싶다. 중력, 뛰는 힘, y속도
public class PlayerMove : MonoBehaviour
{
    // -크기
    public float speed = 5;

    public float gravity = -9.81f;
    public float jumpPower = 10;
    float yVelocity;

    CharacterController cc;

    // Start is called before the first frame update
    void Start()
    {
        cc = GetComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
        // 1. 속도에 중력을 계속 더하고 싶다. -> 중력을 계속 반영해야하기 때문
        yVelocity += gravity * Time.deltaTime;
        // 2. 만약 사용자가 점프버튼을 누르면 y속도에 뛰는 힘을 대입하고 싶다.
        if (Input.GetButtonDown("Jump"))
        {
            yVelocity = jumpPower;
        }
 
        // 1. 사용자의 입력에 따라
        float h = Input.GetAxis("Horizontal"); //좌우
        float v = Input.GetAxis("Vertical"); //앞뒤
        // 2. 방향을 만들고
        Vector3 dir = Vector3.right * h + Vector3.forward * v; //위아래가 아니고 앞뒤라서 forward

        // 카메라가 바라보는 방향을 앞방향으로 하고 싶다.
        dir = Camera.main.transform.TransformDirection(dir);

        // dir크기(vector)를 1로 만들어줌
        dir.Normalize();
        // 3. y속도를 최종 dir의 y에 대입하고싶다.
        dir.y = yVelocity;


        // 3. 그 방향으로 이동하고 싶다.
        // P=P0+vt
        cc.Move(dir * speed * Time.deltaTime); //Move함수는 충돌검사를 먼저해줌
    }
}

 

코드 외에도 컴포넌트 설정하는 부분이나 mesh 설정

애니메이션 설정 등 여러가지 내용이 추가적으로 있지만 너무 양이 많아져서 따로 적진 않음

'etc. > 🕹️unity' 카테고리의 다른 글

VR 실습  (0) 2022.06.11
2D Shooting  (0) 2022.06.11
unity shader, effect, light & 지형 만들기  (0) 2022.06.11
간단 실습 - fps 게임  (0) 2022.06.11
사용자 입력 제어 및 오브젝트 이동 처리  (0) 2022.06.11