Chapter 1. 캐릭터 움직이기 & 지형 제작
캐릭터 움직이기 - 기본
캐릭터의 크기는 캡슐의 크기로 어림 잡으면 편하다.
1 인칭 카메라
Main Camera 오브젝트를 A라는 이름의 오브젝트의 자식으로 넣으면 Main Camera는 A의 1인칭 카메라가 된다. 즉, A 의 시점으로 화면을 비춘다. 카메라의 위치가 A 의 시점이 되도록 Trnasform 값만 조정해주면 A 를 따라다니며(자식이니까) A 의 시점을 촬영하는 1 인칭 카메라가 된다.
📜PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField]
private float walkSpeed;
[SerializeField]
private float lookSensitivity;
[SerializeField]
private float cameraRotationLimit;
private float currentCameraRotationX;
[SerializeField]
private Camera theCamera;
private Rigidbody myRigid;
void Start()
{
myRigid = GetComponent<Rigidbody>(); // private
}
void Update() // 컴퓨터마다 다르지만 대략 1초에 60번 실행
{
Move(); // 1️⃣ 키보드 입력에 따라 이동
CameraRotation(); // 2️⃣ 마우스를 위아래(Y) 움직임에 따라 카메라 X 축 회전
CharacterRotation(); // 3️⃣ 마우스 좌우(X) 움직임에 따라 캐릭터 Y 축 회전
}
private void Move()
{
float _moveDirX = Input.GetAxisRaw("Horizontal");
float _moveDirZ = Input.GetAxisRaw("Vertical");
Vector3 _moveHorizontal = transform.right * _moveDirX;
Vector3 _moveVertical = transform.forward * _moveDirZ;
Vector3 _velocity = (_moveHorizontal + _moveVertical).normalized * walkSpeed;
myRigid.MovePosition(transform.position + _velocity * Time.deltaTime);
}
private void CameraRotation()
{
float _xRotation = Input.GetAxisRaw("Mouse Y");
float _cameraRotationX = _xRotation * lookSensitivity;
currentCameraRotationX -= _cameraRotationX;
currentCameraRotationX = Mathf.Clamp(currentCameraRotationX, -cameraRotationLimit, cameraRotationLimit);
theCamera.transform.localEulerAngles = new Vector3(currentCameraRotationX, 0f, 0f);
}
private void CharacterRotation() // 좌우 캐릭터 회전
{
float _yRotation = Input.GetAxisRaw("Mouse X");
Vector3 _characterRotationY = new Vector3(0f, _yRotation, 0f) * lookSensitivity;
myRigid.MoveRotation(myRigid.rotation * Quaternion.Euler(_characterRotationY)); // 쿼터니언 * 쿼터니언
// Debug.Log(myRigid.rotation); // 쿼터니언
// Debug.Log(myRigid.rotation.eulerAngles); // 벡터
}
}
🙋♀️ WASD, 방향키 키보드 입력에 따라 이동
[SerializeField]
private float walkSpeed;
private Rigidbody myRigid;
void Start() // 게임이 처음 시작될 때 1회 실행
{
myRigid = GetComponent<Rigidbody>();
}
...
private void Move()
{
float _moveDirX = Input.GetAxisRaw("Horizontal");
float _moveDirZ = Input.GetAxisRaw("Vertical");
Vector3 _moveHorizontal = transform.right * _moveDirX;
Vector3 _moveVertical = transform.forward * _moveDirZ;
Vector3 _velocity = (_moveHorizontal + _moveVertical).normalized * walkSpeed;
myRigid.MovePosition(transform.position + _velocity * Time.deltaTime);
}
유니티에서는 X 축 이동 👉 좌우, Z 축 이동 👉 앞뒤, Y 축 이동 👉 위아래
- walkSpeed 👉 이동 속도(스칼라)
- myRigid 👉 Rigidbody 컴포넌트를 할당할 변수로, private 하기 때문에 게임 시작시 GetComponent 함수를 통해 할당해준다.
- _moveDirX 👉 좌우 이동 크기(스칼라)
- A 혹은 ← 입력시 -1
- 입력 X시 0
- D 혹은 → 입력시 1
- _moveDirZ 👉 앞뒤 이동 크기(스칼라)
- S 혹은 ↓ 입력시 -1
- 입력 X시 0
- W 혹은 ↑ 입력시 1
- _moveHorizontal 👉 좌우 이동 벡터 값
- 방향 * 크기 = transform.right * _moveDirX
- transform.right 은 나(이 스크립트가 붙어있는 캡슐)의 오른쪽 방향을 나타내는 단위 벡터
- 방향 * 크기 = transform.right * _moveDirX
- _moveVertical 👉 앞뒤 이동 벡터 값
- 방향 * 크기 = transform.right * _moveDirX
- transform.forward 은 나(이 스크립트가 붙어있는 캡슐)의 앞쪽 방향 을 나타내는 단위 벡터
- 방향 * 크기 = transform.right * _moveDirX
- _velocity 👉 속도 벡터
- 방향 x 속도 크기
- (좌우 이동 벡터 + 앞뒤 이동 벡터).normalized = 이동할 방향
- 속도 크기를 나타내는 float 타입인 walkSpeed를 곱한다.
- 방향 x 속도 크기
- (현재의 위치 + 속도 X 델타 타임) 위치로 이동한다.
- Move() 함수는, 컴퓨터 환경에 따라 다르지만 보통 1 초에 60번 실행되는 Update() 함수 내부에서 실행될 것이기 때문에 1초 동안 총 _velocity이 더해질 수 있도록 1 프레임당 1/60 * _velocity 만큼 더해주어야 한다. Time.deltaTime이 1/60가 된다.
🙋♂️ 회전축 개념
해당 축을 고정한 체, 해당 축의 입장에서 시계 방향으로 회전한다. 따라서 X 축 회전은 고개를 숙이는게 Positive 방향 회전이다.
- X 축 회전
- X 축을 중심으로 회전한다는 의미. X 축 회전값은 변함 없다.
- X 축은 평면 가로축(좌우)이니 가로로 긴 평행봉을 잡고 구른다고 생각해보자. 앞으로 숙이고 뒤로 넘어가는 회전하는 것과 같다.
- 고개를 젖히고 숙이는 것이 바로 X 축 회전이라 할 수 있겠다.
- 따라서 마우스를 위아래로 움직이면 고개를 숙였다 올렸다 하는 1인칭 시점을 구현해야 하므로 오브젝트의 머리에 달려있는 1인칭 카메라인 Main Camera를 X 축 회전해주어야 한다.
- 캡슐(나 자신)은 가만히 있는 상태에서 카메라만 X 축으로 회전시키는 것이다.
- 따라서 마우스를 위아래로 움직이면 고개를 숙였다 올렸다 하는 1인칭 시점을 구현해야 하므로 오브젝트의 머리에 달려있는 1인칭 카메라인 Main Camera를 X 축 회전해주어야 한다.
- Y 축 회전
- Y 축을 중심으로 회전한다는 의미. Y 축 회전값은 변함 없다.
- Y 축은 수직 축이니 전봇대를 잡고 빙글 빙글 회전하는 것과 같다. 😭
- 따라서 마우스를 양옆으로 움직이면 고개를 좌우로 돌리는 1인칭 시점을 구현해야 하므로 나 자신을 Y 축 회전해주어야 한다.
- 카메라는 가만히 있고 캡슐(나 자신)은 Y 축으로 회전. 몸을 마우스가 향하는 방향으로 돌림.
- 따라서 마우스를 양옆으로 움직이면 고개를 좌우로 돌리는 1인칭 시점을 구현해야 하므로 나 자신을 Y 축 회전해주어야 한다.
- Z 축 회전
- Z 축을 중심으로 회전한다는 의미. Z 축 회전값은 변함 없다.
- Z 축은 평면 세로축(앞뒤)이니 양 옆으로 굴러가는 회전을 하는 것과 같다.
- 회전 축 잘 모르겠다면 직접 오브젝트의 회전축을 잡고 이리저리 조작해보자.
🙋♀️ 마우스 위아래(Y)로 움직일시 카메라 X 축 회전
[SerializeField]
private float lookSensitivity; // 민감도
[SerializeField]
private float cameraRotationLimit; // 카메라를 일정 각도 이상으로 회전 못시키게 하는 제한 값.
private float currentCameraRotationX; // 카메라의 X 축 회전값. 초기값은 정면을 보게끔 기본값 0.
[SerializeField]
private Camera theCamera;
...
private void CameraRotation()
{
float _xRotation = Input.GetAxisRaw("Mouse Y");
float _cameraRotationX = _xRotation * lookSensitivity;
// currentCameraRotationX += _cameraRotationX; 마우스 Y 반전
currentCameraRotationX -= _cameraRotationX;
currentCameraRotationX = Mathf.Clamp(currentCameraRotationX, -cameraRotationLimit, cameraRotationLimit);
theCamera.transform.localEulerAngles = new Vector3(currentCameraRotationX, 0f, 0f);
}
- theCamera는 메인카메라가 할당 된다. 컴포넌트가 아니라서 GetComponent로 가져올 수가 없어서 private이지만 유니티 에디터에서 할당해줄 수 있도록 [SerializField] 속성을 붙여주었다.
- Hierarchy 창에서 해당 하는 오브젝트를 찾아주는 FindObjectOfType 함수를 사용할 수도 있지만 오브젝트가 굉장히 많다면 너무 느리고 카메라도 여러개를 둘 수도 있기 때문에 이 방법은 비추.
- 마우스 커서는 2D다. X 축, Y 축 이동 밖에 없다.
- Input.GetAxisRaw(“Mouse Y”) 역시 -1, 0, 1만 나타낸다.
- 마우스가 아래로 움직이면 -1
- 마우스가 위아래로 움직이지 않았으면 0
- 마우스가 위로 움직이면 1
- 카메라가 X 축으로 회전 할 만큼의 양 _cameraRotationX
- 마우스가 Y방향으로 움직인 정도(_xRotation) * 민감도
- _xRotation은 -1, 0, 1 값중 하나다.
- Update 함수 내부에서 실행될 것이라서 1 프레임에 이만큼 회전하는게 된다.
- 마우스가 Y방향으로 움직인 정도(_xRotation) * 민감도
- 카메라의 X 축 방향 회전 값 currentCameraRotationX
- 1️⃣ _cameraRotationX만큼 빼준다.
- x 축 방향으로 회전할 때 아래로 숙이는게 Positive 방향이고 정면을 보는게 0 이고 위로 젖히는게 Negative 방향이다.
- 마우스가 아래로 움직이면 _cameraRotationX 값이 Input.GetAxisRaw에 의해 음수가 되기 때문에 _cameraRotationX만큼을 더해주게 되면 currentCameraRotationX 값이 감소하여 카메라가 위로 회전하게 된다. 따라서 +=로 더해주게 되면 오히려 마우스를 아래로 내릴 수록 위를 쳐다보게 된다.
- 여기선 마우스가 아래로 움직이면 카메라의 x축 회전 값이 커져 카메라가 아래로 숙이게끔 -= 빼주었다.
- 보통 FPS 게임에 이런 마우스 상하 반전 설정 항목이 있다.
- x 축 방향으로 회전할 때 아래로 숙이는게 Positive 방향이고 정면을 보는게 0 이고 위로 젖히는게 Negative 방향이다.
- 2️⃣ Mathf.Clamp를 사용하여 currentCameraRotationX가 (-cameraRotationLimit ~ cameraRotationLimit) 범위 안에만 있도록 한다.
- 마우스를 위아래로 움직일때 360도 회전해버리면 안되고 고개를 젖힐때나 숙일때나 어느정도 제한을 두어야 해서
- 1️⃣ _cameraRotationX만큼 빼준다.
- 카메라의 회전 값 설정 👉 (currentCameraRotationX, 0, 0)으로 설정해주기
- 카메라 회전 값 (Vector3로 변환) theCamera.transform.localEulerAngles
🙋♀️ 마우스 좌우(X)로 움직일시 나 자신을 Y 축 회전
private void CharacterRotation() // 좌우 캐릭터 회전
{
float _yRotation = Input.GetAxisRaw("Mouse X");
Vector3 _characterRotationY = new Vector3(0f, _yRotation, 0f) * lookSensitivity;
myRigid.MoveRotation(myRigid.rotation * Quaternion.Euler(_characterRotationY)); // 쿼터니언 * 쿼터니언
// Debug.Log(myRigid.rotation); // 쿼터니언
// Debug.Log(myRigid.rotation.eulerAngles); // 벡터
}
- 두 쿼터니언의 회전량을 더하려면, 두 쿼터니언 값을 곱해야 한다.
컴포넌트 설정
- 캡슐 오브젝트에
- Rigidbody 컴포넌트를 붙여준다.
- 캡슐 오브젝트는 물리 기능을 가지게 된다. (중력, 마찰력 등등이 적용 됨)
- 캡슐 오브젝트가 밑이 동그래서 자꾸 양옆(Z축 회전),앞뒤(X축 회전)로 넘어지므로 X, Z 회전은 안되게끔 막아두었다.
- 📜PlayerController.cs 를 붙여준다.
- 캡슐 오브젝트는 이제 아래와 같은 기능을 가지게 된다.
- 키보드 입력에 따른 이동
- 마우스 Y 회전에 따른 카메라의 X 축 회전
- 마우스 X 회전에 따른 캡슐의 Y 축 회전
- 캡슐 오브젝트는 이제 아래와 같은 기능을 가지게 된다.
- Rigidbody 컴포넌트를 붙여준다.
'C# > UNITY_FPS 3D 서바이벌게임' 카테고리의 다른 글
Chapter 2-2. 무기 : 총 구현 2️⃣ (재장전, 정조준, 반동) (0) | 2024.03.11 |
---|---|
Chapter 2-1. 무기 : 총 구현 1️⃣ (애니메이션, 파티클, 오디오) (0) | 2024.03.11 |
Chapter 1-4. 캐릭터, 지형 : 팔 구현, 팔 애니메이션, 서브 카메라 (0) | 2024.03.11 |
Chapter 1-3. 캐릭터, 지형 : 지형 제작 (0) | 2024.03.11 |
Chapter 1-2. 캐릭터, 지형 : 달리기, 부드럽게 앉기, 점프 (0) | 2024.03.11 |