<Camera #1>
:일반적으로 RPG 게임에서 Camera가 Player를 따라가면서 비춰야한다. -> 여러개의 카메라를 두어
- Player 전용 Camera
1. unity chan에 Main Camera를 넣어서 다음과 같이 설정한다.
2. Player 내부에 Main Camera가 있기 때문에 Play를 하면 Main Camera가 Player를 따라가지만, Main Camera의 Rotation이 돌아가면서 정신 없어 보인다.
=> Player의 Rotation에 상관없이 카메라를 동작해야함.
- Player 전용 Camera 좀 더 편안하게 동작
1. Scripts 파일 아래에 Controllers 폴더를 만들고 Controller.cs 파일을 다 넣는다.
2. CameraController.cs 을 만든 후, 다음과 같이 코드를 짠다.
카메라의 mode를 기본적으로 QuaterView로 정의를 하고 delta는 Player와 Camera가 얼마나 떨어져있느냐를 나타내고, GameObject는 해당 카메라가 바라보는 Object를 설정한다.
Camera의 Position을 Player position에 delta만큼 더하여 설정하고, 카메라가 바라보는 것을 LookAt을 이용하여 설정한다.
* CameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
Define.CameraMode _mode = Define.CameraMode.QuarterView;
[SerializeField]
Vector3 _delta; // Player와 Camera가 얼마나 떨어져있냐?
[SerializeField]
GameObject _player;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
// Player 기준으로 계산
void Update()
{
transform.position = _player.transform.position + _delta; // Player의 position을 가져온 후, delta 만큼을 더함
transform.LookAt(_player.transform); // LookAt: 상대 game object를 보도록 설정
}
}
*Define.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Define
{
public enum CameraMode // 우리가 사용할 CameraMode들을 정의
{
QuarterView,
}
}
=> 이렇게 한 후, 실행하면 Player가 덜덜거리는 문제가 발생한다. PlayerController에서 update로 Player가 이동하고, CameraController에서 update로 Player가 이동하면서 어떤것이 먼저 동작할지 알 수 없음. 1/2 확률로 왔다갔다하여 덜덜거리는 현상이 나타남
* 해결: 무조건 Player의 update가 끝난 후에 Camera update -> LateUpdate()
void LateUpdate()
{
transform.position = _player.transform.position + _delta; // Player의 position을 가져온 후, delta 만큼을 더함
transform.LookAt(_player.transform); // LookAt: 상대 game object를 보도록 설정
}
Update 다음에 LaterUpdate 실행
*최종 코드: CamaraController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
Define.CameraMode _mode = Define.CameraMode.QuarterView;
[SerializeField]
Vector3 _delta; // Player와 Camera가 얼마나 떨어져있냐?
[SerializeField]
GameObject _player = null;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
// Player 기준으로 계산
void LateUpdate()
{
// QuaterView일 경우 다음과 같이 동작
if(_mode == Define.CameraMode.QuarterView)
{
transform.position = _player.transform.position + _delta; // Player의 position을 가져온 후, delta 만큼을 더함
transform.LookAt(_player.transform); // LookAt: 상대 game object를 보도록 설정
}
}
// delta를 받아서 QuaterView를 설정해주는 함수
public void SetQuaterView(Vector3 delta)
{
_mode = Define.CameraMode.QuarterView;
_delta = delta;
}
}
<Camera#2>
: 마우스가 어떤 한 포인트를 가르키면 해당 포인트로 Player를 이동하는 동작
- Manager 변경
1. InputManager에 지금까지 있었던 KeyAction 말고도 MouseAction을 다음과 같이 추가한다.
Define에 Mouse event에 대한 정의를 Click과 Press를 추가한다.
만약 마우스의 왼쪽 버튼이 눌리면 Press event를 전파하고 왼쪽 버튼이 안눌렸다면, click 한 순간인지 판단한다.
*Define.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Define
{
public enum MouseEvent // MouseEvent 분류
{
Press,
Click,
}
public enum CameraMode // 우리가 사용할 CameraMode들을 정의
{
QuarterView,
}
}
* InputManager.cs
using System.Collections;
using System; // Action 을 위해
using System.Collections.Generic;
using UnityEngine;
public class InputManager
{
public Action KeyAction = null;
public Action<Define.MouseEvent> MouseAction = null;
// mouse의 상태를 저장하는 boolean 변수
bool _pressed = false;
// Update is called once per frame
public void OnUpdate()
{
/* 아무 key도 누르지않은 상태
if (Input.anyKey == false)
return;
*/
// mouse를 떼는 부분도 action이라고 안식해야하므로 Input.anyKey
if (Input.anyKey && KeyAction != null)
KeyAction.Invoke(); // keyaction이 있었다고 전파
if(MouseAction != null)
{
if (Input.GetMouseButton(0)) // 왼쪽 클릭 0번
{
MouseAction.Invoke(Define.MouseEvent.Press); // Press event를 알려줌
_pressed = true;
}
else
{
// Mouse를 눌렀다가 떼는 순간이 click
if (_pressed)
MouseAction.Invoke(Define.MouseEvent.Click);
_pressed = false;
}
}
}
}
2. PlayerController에서 keyboard에 대한 코드 대신, Mouse에 대한 Manager도 추가한다.
Mouse event가 발생할 경우, 다음과 같이 Raycasting을 이용한다. Raycasting으로 카메라가 찍힌 곳의 좌표를 가져와야한다.
hit.point를 통해 마우스가 찍힌 곳의 좌표를 가져와서 _destPos 벡터에 저장한다. 해당 목적지로 이동해야하는지에 대한 것을 bool 변수인 _moveToDest에 저장한다.
Update() 함수를 통해 _moveToDest가 true일시, 해당 목적지로 position이동
* PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField]
float _speed = 10.0f;
bool _moveToDest = false;
Vector3 _destPos; // 옮겨가야할 포인트
// Start is called before the first frame update
void Start()
{
Managers.Input.KeyAction -= OnKeyboard;
Managers.Input.KeyAction += OnKeyboard;
Managers.Input.MouseAction -= OnMouseClicked;
Managers.Input.MouseAction += OnMouseClicked;
}
float _yAnlgle = 0.0f;
// Update is called once per frame
void Update()
{
if (_moveToDest)
{
Vector3 dir = _destPos - transform.position; // 현재 위치에서 최종 도착해야할 위치를 - : 방향 벡터
if(dir.magnitude < 0.0001f) // 그 차이가 아주 작으면 -> 도착
{
_moveToDest = false;
}
else
{ //dir normalized
transform.position += dir.normalized * _speed * Time.deltaTime;
transform.LookAt(_destPos);
}
}
}
void OnKeyboard()
{
if (Input.GetKey(KeyCode.W))
{
// W를 누르면 해당 위쪽(북쪽)방향을 바라보도록 rotation 변경
//transform.rotation = Quaternion.LookRotation(Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), 0.2f);
transform.position += (Vector3.forward * Time.deltaTime * _speed); // 시간 x 거리 = 속력
}
if (Input.GetKey(KeyCode.S))
{
//transform.rotation = Quaternion.LookRotation(Vector3.back);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
transform.position += (Vector3.back * Time.deltaTime * _speed);
}
if (Input.GetKey(KeyCode.A))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.left), 0.2f);
//transform.rotation = Quaternion.LookRotation(Vector3.left);
transform.position += (Vector3.left * Time.deltaTime * _speed);
}
if (Input.GetKey(KeyCode.D))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), 0.2f);
//transform.rotation = Quaternion.LookRotation(Vector3.right);
transform.position += (Vector3.right * Time.deltaTime * _speed);
}
// keyboard에서 목적지로 이동하는 방식이 아님
_moveToDest = false;
}
void OnMouseClicked(Define.MouseEvent evt)
{
if (evt != Define.MouseEvent.Click)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); ;// Screen의 좌표를 바로 World 좌표로 가져옴
Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall"); // Scene의 Plane의 Layer를 "Wall"로 설정
// int mask = (1 << 8) | (1 << 9);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
{
_destPos = hit.point; // 마지막 도착 지점을 Mouse가 point한 점으로 바꿔야함.
_moveToDest = true;
//Debug.Log($"Raycast Camera @ {hit.collider.gameObject.tag}");
}
}
}
=> 이렇게 하면 Player가 해당 목적지에서 제자리에서 왔다갔다하는 현상 발생
why?) transform.position += dir.normalized * _speed * Time.deltaTime; 에서 Speed를 곱한 값이 목적지보다 더 크기 때문에
아래의 코드로 바꿔줌
Mathf.clamp(value, float min, float max) value가 min보다 작으면 min, max보다 크면 max로 변함
transform.position += dir.normalized * moveDist;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField]
float _speed = 10.0f;
bool _moveToDest = false;
Vector3 _destPos; // 옮겨가야할 포인트
// Start is called before the first frame update
void Start()
{
Managers.Input.KeyAction -= OnKeyboard;
Managers.Input.KeyAction += OnKeyboard;
Managers.Input.MouseAction -= OnMouseClicked;
Managers.Input.MouseAction += OnMouseClicked;
}
float _yAnlgle = 0.0f;
// Update is called once per frame
void Update()
{
if (_moveToDest)
{
Vector3 dir = _destPos - transform.position; // 현재 위치에서 최종 도착해야할 위치를 - : 방향 벡터
if(dir.magnitude < 0.0001f) // 그 차이가 아주 작으면 -> 도착
{
_moveToDest = false;
}
else
{
float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude); // clamp(value, float min, float max) value가 min보다 작으면 min, max보다 크면 max로 변함
transform.position += dir.normalized * moveDist;
transform.LookAt(_destPos);
}
}
}
void OnKeyboard()
{
if (Input.GetKey(KeyCode.W))
{
// W를 누르면 해당 위쪽(북쪽)방향을 바라보도록 rotation 변경
//transform.rotation = Quaternion.LookRotation(Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), 0.2f);
transform.position += (Vector3.forward * Time.deltaTime * _speed); // 시간 x 거리 = 속력
}
if (Input.GetKey(KeyCode.S))
{
//transform.rotation = Quaternion.LookRotation(Vector3.back);
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
transform.position += (Vector3.back * Time.deltaTime * _speed);
}
if (Input.GetKey(KeyCode.A))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.left), 0.2f);
//transform.rotation = Quaternion.LookRotation(Vector3.left);
transform.position += (Vector3.left * Time.deltaTime * _speed);
}
if (Input.GetKey(KeyCode.D))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), 0.2f);
//transform.rotation = Quaternion.LookRotation(Vector3.right);
transform.position += (Vector3.right * Time.deltaTime * _speed);
}
// keyboard에서 목적지로 이동하는 방식이 아님
_moveToDest = false;
}
void OnMouseClicked(Define.MouseEvent evt)
{
if (evt != Define.MouseEvent.Click)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); ;// Screen의 좌표를 바로 World 좌표로 가져옴
Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall");
// int mask = (1 << 8) | (1 << 9);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
{
_destPos = hit.point; // 마지막 도착 지점을 Mouse가 point한 점으로 바꿔야함.
_moveToDest = true;
//Debug.Log($"Raycast Camera @ {hit.collider.gameObject.tag}");
}
}
}
<Camera#3>
플레이어가 벽에 막혔을때, 카메라의 위치를 Player 앞으로 이동
- rotation을 자연스럽게
1. 다음과 같이 Quaternion.Slerp(이전 시간에 배운)을 이용하여 rotation을 스무스하게 바꿈
void Update()
{
if (_moveToDest)
{
Vector3 dir = _destPos - transform.position; // 현재 위치에서 최종 도착해야할 위치를 - : 방향 벡터
if(dir.magnitude < 0.0001f) // 그 차이가 아주 작으면 -> 도착
{
_moveToDest = false;
}
else
{
float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude); // clamp(value, float min, float max) value가 min보다 작으면 min, max보다 크면 max로 변함
transform.position += dir.normalized * moveDist;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
}
}
}
- 벽에 막혔을때, 카메라 앞당기기
1. 다음과 같이 Cube를 복사하여 벽을 만든다.
2. 구현 방안
: 카메라에서 레이저를 쐈을때, 레이저가 Collsion이 발생하면 Player 앞으로 카메라 이동 => CameraController.cs 수정
LaterUpdate 함수에서 Camera Raycasting 조정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
[SerializeField]
Define.CameraMode _mode = Define.CameraMode.QuarterView;
[SerializeField]
Vector3 _delta; // Player와 Camera가 얼마나 떨어져있냐?
[SerializeField]
GameObject _player = null;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
// Player 기준으로 계산
void LateUpdate()
{
// QuaterView일 경우 다음과 같이 동작
if(_mode == Define.CameraMode.QuarterView)
{
RaycastHit hit;
// 카메라가 Raycast를 통해 벽을 만남
if(Physics.Raycast(_player.transform.position, _delta, out hit, _delta.magnitude, LayerMask.GetMask("Wall")))
{
Debug.Log("Camera Collsion 발생");
float dist = (hit.point - _player.transform.position).magnitude * 0.8f; // 0.8만큼 앞으로 가도록
transform.position = _player.transform.position + _delta.normalized * dist;
}
else
{
transform.position = _player.transform.position + _delta; // Player의 position을 가져온 후, delta 만큼을 더함
transform.LookAt(_player.transform); // LookAt: 상대 game object를 보도록 설정
}
}
}
// delta를 받아서 QuaterView를 설정해주는 함수
public void SetQuaterView(Vector3 delta)
{
_mode = Define.CameraMode.QuarterView;
_delta = delta;
}
}
카메라는 내가 만들고 싶은 게임에 따라 다양하게 구현 가능
'Development > 유니티' 카테고리의 다른 글
[섹션6] 애니메이션(Animation) - 심화 (0) | 2021.07.22 |
---|---|
[섹션6] 애니메이션(Animation) (0) | 2021.07.21 |
[섹션4] Collsion(충돌) (0) | 2021.07.09 |
[섹션 3] 프래팹(Prefab) (0) | 2021.07.08 |
[섹션2] Transform(트랜스폼) (0) | 2021.07.06 |