EasyCastleUNITY

3D SpaceShooter Player Control & FollowCam 본문

유니티 심화

3D SpaceShooter Player Control & FollowCam

EasyCastleT 2023. 8. 18. 00:05

1. WASD를 통해 플레이어 이동

2. 마우스를 왼쪽 클릭하고 드래그하면 화면이 돌아간다

3. 마우스 휠을 스크롤하면 화면을 줌인 줌아웃 한다. 

4. 키보드 1번과 2번은 화면의 각도를 변경한다. (위 아래로 움직임)

전체적인 화면

PlayerControl: 플레이어 움직임

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

public class PlayerControl : MonoBehaviour
{
    public enum eControlType
    {
        KeyBoard,JoyStick
    }

    public enum eAnimState
    {
        Idle, RunB, RunF, RunL, RunR
    }
    private Transform transform;
    [SerializeField]
    private FollowCam cam;
    private float dampingCoefficient = 0.96f;
    public float moveSpeed = 10.0f;
    public float turnSpeed = 0.0f;
    [SerializeField]
    private VariableJoystick joystick;
    [SerializeField]
    private eControlType controlType;
    private Animation anim;

    private Vector3 startPos; // down 했을 때 위치 
    private Vector3 endPos; //up 했을 때 위치 


    // Start is called before the first frame update
    void Start()
    {
        this.transform = GetComponent<Transform>();
        this.anim = GetComponent<Animation>();
        this.startPos = new Vector3(0, 0, 0);
        this.endPos = new Vector3(0, 0, 0);
        //this.anim.clip= this.anim.GetClip(eAnimState.Idle.ToString());
        //this.anim.Play();
    }

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

        float h = 0;
        float v = 0;
        //키보드
        if(this.controlType == eControlType.KeyBoard)
        {
            //GetAxis -> 키보드 입력에 대해서 정보를 제공
            h = Input.GetAxisRaw("Horizontal"); // -1 ,0, 1
            v = Input.GetAxisRaw("Vertical"); // -1 ,0, 1
        }
        //조이스틱
        else if(this.controlType == eControlType.JoyStick)
        {
            h = this.joystick.Direction.x;
            v = this.joystick.Direction.y;
        }
        //Debug.Log(this.joystick.Direction); // x,y
        
        Vector3 dir = new Vector3(h, 0, v);
        float angle =Mathf.Atan2(dir.x,dir.z) * Mathf.Rad2Deg; //라디안으로 나온 값, 각도로 변경

        //회전
        if(Input.GetMouseButtonDown(0))
        {
            this.startPos = Input.mousePosition;
        }
        else if (Input.GetMouseButton(0)) //드래그 하는 동안 회전 
        {
            this.endPos = Input.mousePosition;
            float swipeLength = this.endPos.x - this.startPos.x;
            this.turnSpeed = swipeLength / 500f;
            this.cam.isTurn = true;
        }
        else this.cam.isTurn = false;

        //회전
        //this.tr.localRotation = Quaternion.AngleAxis(angle, Vector3.up);
        this.transform.Rotate(Vector3.up * this.turnSpeed);
        this.turnSpeed *= this.dampingCoefficient;

        if (dir != Vector3.zero ) //키보드 누르고 있을때
        {
            //이동
            this.transform.Translate(dir * this.moveSpeed * Time.deltaTime);
            this.cam.isMove = true;
        }
        else this.cam.isMove = false;
        this.PlayAnimation(dir);
    }
    //애니메이션
    private void PlayAnimation(Vector3 dir)
    {
        if (dir.x > 0)
        {
            //right
            this.anim.CrossFade(eAnimState.RunR.ToString(), 0.25f);
        }
        else if (dir.x < 0)
        {
            //left
            this.anim.CrossFade(eAnimState.RunL.ToString(), 0.25f);
        }
        else if(dir.z > 0)
        {
            //forward
            this.anim.CrossFade(eAnimState.RunF.ToString(), 0.25f);

        }
        else if(dir.z < 0)
        {
            //back
            this.anim.CrossFade(eAnimState.RunB.ToString(), 0.25f);
        }
        else
        {
            //Idle
            this.anim.CrossFade(eAnimState.Idle.ToString(), 0.25f);
        }
    }
}

FollowCam: 카메라가 플레이어 쫓아감,  줌인 줌아웃 ,오프셋

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

public class FollowCam : MonoBehaviour
{
    //타겟은 플레이어 
    [SerializeField]
    private Transform playerTrans;


    [SerializeField]
    private float distance = 10f;
    private float height=3f;
    [SerializeField]
    private float scrollSpeed = 100f;

    [SerializeField]
    private Transform zoomInPoint;
    [SerializeField]
    private Transform zoomOutPoint;
    [SerializeField]
    [Range(0f, 1f)]
    private float lerpValue =0.5f;
    private Vector3 tpos;
    [SerializeField]
    private Vector3 offSet;

    public bool isMove = false;
    public bool isTurn = false;
    private bool isScroll = false;
    private bool isInit = true;
    // Start is called before the first frame update
    void Start()
    {
        this.offSet = Vector3.Lerp(Vector3.zero, new Vector3(0, 1.88f, 0), 1.0f);
        //this.SetPosition();
        this.isInit = true;
    }

    // Update메서드 이후의 실행
    void LateUpdate()
    {
        this.SetOffset();
        this.SetPosition();
        this.CameraWheelMove();
    }
    //위치 세팅
    private void SetPosition()
    {

        Vector3 x = this.playerTrans.position + this.playerTrans.forward * -1 * this.distance;
        
        this.tpos = x + Vector3.up * height;
        this.transform.position = this.tpos;
        height = this.transform.position.y;
        
        this.transform.LookAt(this.playerTrans.position + this.offSet);

        this.zoomInPoint.transform.position =
           this.transform.position + this.transform.forward * 2f;
        this.zoomOutPoint.transform.position =
            this.transform.position + this.transform.forward * -5f;
        this.isInit = false;
    }
    //마우스 휠을 스크롤하면 마우스를 줌인 줌아웃
    private void CameraWheelMove()
    {

        float scroll = Input.GetAxis("Mouse ScrollWheel");
        //Debug.LogFormat("scroll:{0}",scroll); // 가까워지면 0.1, 멀어지면 -0.1
        if (scroll > 0)
        {
            this.lerpValue -= 0.05f;
            if(this.lerpValue <= 0) this.lerpValue = 0;
        }
        else if (scroll < 0)
        {
            this.lerpValue += 0.05f;
            if(this.lerpValue >=1) this.lerpValue = 1;
        }

        this.tpos = Vector3.Lerp(this.zoomInPoint.position, this.zoomOutPoint.position,
                this.lerpValue);
        this.transform.position = this.tpos;
        this.isInit = false;
    }
    //카메라 오프셋
    //1번 누르면 플레이어 머리를 포커스
    //2번 누르면 플레이어 발을 포커스
    private void SetOffset()
    {
        if (Input.GetKey(KeyCode.Alpha1))
        {
            //offset위로 
            this.offSet.y += 0.03f;
            if (this.offSet.y > 1.88f) this.offSet.y = 1.88f;
        }
        else if (Input.GetKey(KeyCode.Alpha2))
        {
            //offset 아래로
            this.offSet.y -= 0.03f;
            if (this.offSet.y < 0) this.offSet.y = 0.0f;
        }
    }
}

https://www.youtube.com/watch?v=LBc79tGaAwc 시연영상

 

어려웠던 점

1. 스크롤을 통해서 카메라 줌인 줌아웃 하는 기능을 구현하는 게 힘들었다. 

    처음에는 그냥 Translate를 사용하면 끝나는 기능인 줄 알았으나, 도중에 사용된 LookAt 함수 때문에

    Vector3.forward를 활용하면, 카메라의 각도가 계속 바뀌는 일이 생겼다. 

    이 점을 해결한 것이 오늘 배운 Vector3.Lerp 였다. 

    이 Lerp를 활용하여 줌인 줌아웃을 구현해보니 어떤 식으로 벡터를 활용해야 될지 감이 잡혔다. 

1-1. 인스펙터의 슬라이드 구현 

코드
인스펙터에서 보이는 부분

이런식으로 범위를 정해서 슬라이드를 생성해준다. 여기서는 lerpValue를 응용하기위해 0~1까지로 구성하였다. 

그런데 이런식으로 제한을 걸어도 실제 변수 lerpValue의 값은 제한이 안된다는 것을 알았다. 

코드, 스크롤 했을 때 lerpValue 변화를 출력
실제 스크롤 했을 때, 실제 lerpValue의 변화

이런식으로 인스펙터 상에서 슬라이드의 값이 제한된다고 해도, 실제 lerpValue 값은 그 값을 넘어 계산되고 있다. 

이점을 유의하여 스크롤을 통해 줌인 줌아웃을 만들었다. 

그래서 이런 식으로 lerpValue의 값을 제한 시켰다.

새로 배운것

1.Vector3.Lerp, 벡터의 활용법

2.레거시 애니메이션 사용법

3.LOD (Level of Detail)

 사용자에게서 멀다면, 굳이 렌더링 될 필요가 없기에, 이것을 위해 제공하는 기능, 사용자 카메라에서 먼 오브젝트의 디테일이 떨어지고, 멀리 떨어지면 완전히 Culling되어 렌더링 되지 않는다.