DINO NET

[Unity] FPS 코드 백업 본문

program()/Unity

[Unity] FPS 코드 백업

2023. 10. 12. 15:25

FPS 게임 만들 때 사용했었던 코드

 

1. player의 움직임 구현 (PlayerMove.cs)

- 키보드의 입력 값을 받아 player 움직임

- 점프 space로 구현 (중력 계산)

2. 카메라 조정 (PlayerRotate.cs, CamFollow.cs, CamRotate.cs)

- camera가 player에 붙는 1인칭. 마우스로 시선을 따라가는 느낌.

- camera가 수직으로 넘어가지 않게 상하 제한 두기 (y축 회전 제한)

- 회전은 Quatation!

3. 공격 및 체력 시스템 (PlayerFire.cs, EnemyFSM.cs)

-  마우스로 클릭 -> ray로 mass 감지(건물 및 적에게 rigidbody가 존재해야함) -> 부딪힌 rigidbody에 이펙트 효과(총알이 맞는 효과)

- player의 공격으로 ray에 감지되면, 총이 갖는 공격력만큼 적의 체력 깎기

- player 또한 적에게 공격을 당하면 체력 깎기

- Enemy animation system

 (1) Idel: default 상태

 (2) Move: 움직임. 일정 거리 이내에 player 감지 시, 공격을 받았을 경우 player를 따라 옴

 (3) Attack: player가 일정 거리에 도달했을 때, delay 존재

 (4) Return: enemy가 일정 거리를 벗어났을 때, 제자리로 복귀. (rotate까지 챙기는거 잊지 말기)

 (5) Damaged: 해당 애니메이션이 존재하지 않아 잠시 멈추는 것 밖에 구현되지 않음. (체력만 깎임)

 (6) Die: enemy의 체력이 0 이하일 때, 앞 혹은 뒤로 넘어짐. 이후 Dieprocess를 거쳐 component 삭제.


PlayerMove.cs

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

public class PlayerMove : MonoBehaviour
{
    //이동 속도
    public float moveSpeed = 5.0f;
    CharacterController cc; //charactercontroller 자체가 unity에서 기본으로 주어지는 물리엔진?
    //중력
    float gravity = -20f;
    //수직 속력 변수
    float yvelocity = 0;
    //점프력 변수
    public float jumpPower = 2.0f;
    //플레이어 체력 변수
    public int hp = 10;

    //플레이어 피격 함수
    //EnemyFSM 스크립트(다른 스크립트)에서 사용할 함수이므로 public값 부여
    public void DamageAction(int damage)
    {
        //enemy의 공격력만큼 체력을 깎기
        hp -= damage;

        //체력이 음수가 될 경우 0으로 초기화
        if(hp < 0)
        {
            hp = 0;
            print("You are die!");
        }

        //player 체력 가시화
        print("Player HP: " + hp);

    }


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

    // Update is called once per frame
    void Update()
    {
        //입력 값을 받음
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");

        //이동 방향 설정
        Vector3 dir = new Vector3(h, 0, v);
        dir = dir.normalized; //정규화 과정을 거쳐서 매끄럽게 만듦

        //main camera를 기준으로 방향 변환
        dir = Camera.main.transform.TransformDirection(dir);
        //main camera의 방향을 가져오는 함수임

        //실제 이동
        //transform.position += dir * moveSpeed * Time.deltaTime;

        //점프를 적용 후에 중력 값을 적용해야 하므로 중력 위에 위치해야 한다.
        //스페이스바 버튼을 누르면 점프
        if(Input.GetButtonDown("Jump"))
        {
            yvelocity = jumpPower;
        }

        //수직 속도에 중력 값 적용
        yvelocity += gravity * Time.deltaTime;
        dir.y = yvelocity;
        cc.Move(dir * moveSpeed * Time.deltaTime); //실제 이동을 cc를 이용해서 다시 계산
    }
}

PlyerRotate.cs

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

public class PlayerRotate : MonoBehaviour
{
    public float rotSpeed = 200f;
    float mx = 0; //몸통이 앞구르기를 할 필요는 없으므로 mx만

    // Update is called once per frame
    void Update()
    {
        float mouse_X = Input.GetAxis("Mouse X");
        mx += mouse_X * rotSpeed * Time.deltaTime;
        transform.eulerAngles = new Vector3(0, mx, 0);
    }
}

PlayerFire.cs

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

public class PlayerFire : MonoBehaviour
{
    //figure effectt object 
    public GameObject bulletEffect;
    //피격 이펙트의 particle system
    ParticleSystem ps; //component 변수는 start에서 할당
    public int weaponPower = 5;

    // Start is called before the first frame update
    void Start()
    {
        ps = bulletEffect.GetComponent<ParticleSystem>();
    }

    // Update is called once per frame
    void Update()
    {
        //마우스 왼쪽 버튼을 누르면 총알 발사
        if(Input.GetMouseButtonDown(0))
        {
            //ray 생성
            //main camera 위치, 앞방향으로 ray 생성
            Ray ray = new Ray(Camera.main.transform.position, Camera.main.transform.forward);
            //ray가 부딪힐 대상 저장
            RaycastHit hitinfo = new RaycastHit();

            //부딪힌 대상이 있다면 피격 이펙트 표시
            if(Physics.Raycast(ray, out hitinfo))
            {
                //만약 ray에 부딪힌 대상의 레이어가 Enemy라면 Damaged 함수 실행
                if(hitinfo.transform.gameObject.layer == LayerMask.NameToLayer("Enemy"))
                {
                    EnemyFSM eFSM = hitinfo.transform.GetComponent <EnemyFSM>();
                    eFSM.hitEnemy(weaponPower);
                }

                //피격 이펙트의 위치를 레이가 부딪힌 지점으로 이동
                bulletEffect.transform.position = hitinfo.point; //ray는 position이 아닌 point로 표시됨
                                                                 //피격 이펙트 play
                ps.Play();
            }
        }
    }
}

CamFollow.cs

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

public class CamFollow : MonoBehaviour
{
    //Cam Position의 Transform
    public Transform target; //외부 값을 가져오므로 public 실시간으로 값을 받아올 것임.

    // Update is called once per frame
    void Update()
    {
        //cam position의 위치에 main camera의 위치가 계속해서 동일하게 따라 오라는 뜻임
        transform.position = target.position;
    }
}

CamRotate.cs

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

public class CanRotate : MonoBehaviour
{
    //회전 속도
    public float rotSpeed = 200f; //float에 f 붙이는거 꼭 기억해두기
    //회전 값
    float mx = 0;
    float my = 0; //마우스를 움직일 때마다 값을 계속해서 누적시켜서 이동할 수 있게 만드는 것임


    // Update is called once per frame
    void Update()
    {
        // 마우스 입력 값을 받기
        float mouse_X = Input.GetAxis("Mouse X"); //input 값을 받는 함수 GetAxis 축 값 받아오고 "" 속 받아올 값 이름
        float mouse_Y = Input.GetAxis("Mouse Y"); //C#은 ;로 닫는거 잊지 말기

        //회전 값 누적
        mx += mouse_X * rotSpeed * Time.deltaTime; //거리 = 속력 * 시간
        my += mouse_Y * rotSpeed * Time.deltaTime;

        //my 값을 -90~90으로 제한 (y축으로 회전하면 뒤집히므로)
        my = Mathf.Clamp(my, -90, 90); //제한해주는 함수

        //실제 회전
        transform.eulerAngles = new Vector3(-my, mx, 0); //eulerAngles -> 회전을 도와줌
                                                         //위 아래로 회전 -> x 값 변환. +와 - 값이 반대임
                                                         //가로로 회전 -> y 값 변환. +와 - 값 그대로
    }
}

EnemyFSM.cs

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

public class EnemyFSM : MonoBehaviour
{
    //enemy 상태 상수
    //여러 상태가 있을 때는 상태 상수들로 정의를 해줌
    enum EnemyState
    {
        Idle, //대기 상태
        Move, //움직임 상태
        Attack, //공격 상태
        Return, //복귀 상태
        Damaged, //피격 상태
        Die //체력 0 죽음상태
    }

    //enemy 상태 변수
    EnemyState m_State;

    //Player 발견 범위
    public float findDistance = 8f;
    //Player.transform 참조
    Transform player; //transform에 직접 접근 가능
                      //GameObject Player; //만약 이렇게 가져오면 transform도 함께 참조해야함

    //공격가능 범위
    public float attackDistance = 2f;
    //Enemy 이동 속도
    public float moveSpeed = 5f;
    CharacterController cc;
    //누적 시간
    float currentTime = 0;
    //공격 딜레이 시간
    float attackDelay = 2f;
    //enemy 공격력
    public int attackPower = 3;

    //enemy 초기 위치 저장
    Vector3 originPos;
    Quaternion originRot; //rotation 값
    //이동 가능 범위
    public float moveDistance = 20f;

    //Enemy 체력
    public int hp = 15;

    //애니메이터 변수
    Animator anim;

    public void hitEnemy(int hitPower)
    {
        hp -= hitPower;
        if (hp > 0)
        {
            m_State = EnemyState.Damaged;
            print("상태 변환: Damaged");
            Damaged();
        }
        else
        {
            m_State = EnemyState.Die;
            print("상태 전환: Die");
            anim.SetTrigger("Die");
            Die();
        }
    }



    // Start is called before the first frame update
    void Start()
    {
        //player transform component 받아오기
        //public으로 player를 선언하였으면 엔진 상에서 드래그 앤 드롭으로 가져올 수 있음
        //but 이번에는 script 상에서 연결해줄 예정
        player = GameObject.Find("Player").transform; //hierarchy에 있는 Player를 가져오기
        //최초의 Enemy 상태를 대기로 설정
        m_State = EnemyState.Idle;
        cc = GetComponent<CharacterController>();
        //자신의 초기 위치 저장
        originPos = transform.position;
        originRot = transform.rotation;
        //Enemy의 자식 object인 zombie의 animator를 가져오기
        anim = transform.GetComponentInChildren<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //프레임마다 현재 상태 체크 후 기능 실행
        switch (m_State)
        {
            case EnemyState.Idle:
                Idle();
                break;

            case EnemyState.Move:
                Move();
                break;

            case EnemyState.Attack:
                Attack();
                break;

            case EnemyState.Return:
                Return();
                break;

            case EnemyState.Damaged:
                break;

            case EnemyState.Die:
                break;
        }
    }

    void Idle()
    {
        //만약 player와 enemy의 거리가 findDistance 이내라면 Move 상태로 전환
        if (Vector3.Distance(transform.position, player.position) < findDistance)
        {
            m_State = EnemyState.Move;
            print("상태 전환: Idle -> Move");
            //이동 애니메이션으로 전환
            anim.SetTrigger("Idle to Move");
        }
    }

    void Move()
    {
        //만약 현재 위치가 초기 위치에서 이동가능 범위를 벗어난다면
        if (Vector3.Distance(transform.position, originPos) > moveDistance)
        {
            m_State = EnemyState.Return;
            print("상태 전환: Move -> Return");
        }
        else if (Vector3.Distance(transform.position, player.position) > attackDistance) //만약 player와 enemy의 거리가 공격범위 밖이라면 이동
        {
            //이동 방향 설정
            Vector3 dir = (player.position - transform.position).normalized;
            //실제 이동
            cc.Move(dir * moveSpeed * Time.deltaTime);
            //플레이어를 향해 방향 전환
            transform.forward = dir;
        }
        else //그렇지 않다면, 현재 상태를 공격으로 전환
        {
            m_State = EnemyState.Attack;
            print("상태 전환: Move -> Attack");
            //누적 시간을 공격 딜레이 시간만큼 미리 진행
            currentTime = attackDelay; //닿자마자 공격 한 번
            anim.SetTrigger("Move to AD");
        }
    }

    void Attack()
    {
        //만약 플레이어가 공격 범위 이내라면 공격 상태
        if (Vector3.Distance(transform.position, player.position) < attackDistance)
        {
            currentTime += Time.deltaTime;
            if (currentTime > attackDelay)
            {
                //플레이어의 PlayerMove 스크립트 중 DamageAction 함수 호출
                player.GetComponent<PlayerMove>().DamageAction(attackPower);
                print("Attack!");
                currentTime = 0;
                anim.SetTrigger("Start Attack");
            }
        }
        else //그렇지 않으면 이동상태로 전환 (재추격)
        {
            m_State = EnemyState.Move;
            print("상태 전환: Attack -> Move");
            currentTime = 0;
            anim.SetTrigger("Attack to Move");
        }
    }

    void Return()
    {
        //만약 초기 위치에서 거리가 0.1 이상이면 초기 위치로 이동
        if (Vector3.Distance(transform.position, originPos) > 0.1)
        {
            Vector3 dir = (originPos - transform.position).normalized;
            cc.Move(dir * moveSpeed * Time.deltaTime);
            transform.forward = dir;
        }
        else
        {
            transform.position = originPos;
            transform.rotation = originRot;
            m_State = EnemyState.Idle;
            print("상태 전환: Return -> Idle");
            anim.SetTrigger("Move to Idle");
        }
    }

    void Damaged()
    {
        StartCoroutine(DamageProcess());
    }

    IEnumerator DamageProcess()
    {
        //0.5초 대기
        yield return new WaitForSecondsRealtime(0.5f);
        //이동 상태로 전환
        m_State = EnemyState.Move;
        print("상태 전환: Damaged -> Move");
        anim.SetTrigger("Damaged to Move");
    }

    void Die()
    {
        StopAllCoroutines();
        StartCoroutine(DieProcess());
    }

    IEnumerator DieProcess()
    {
        cc.enabled = false; //character controller 없앰
        yield return new WaitForSeconds(2f);
        print("소멸");
        Destroy(gameObject); //해당 scene에서 gameObject를 삭제 시킴
    }

}

'program() > Unity' 카테고리의 다른 글

[Unity] 2D 게임 Title, Quit button, Scene 전환  (0) 2023.11.16