이전의 포스트에서 복사한 Locomotion 씬을 실행해보면,
방향키(WASD) 입력에 따라 위아래(WS) 앞뒤 이동, 좌우(AD) 입력에 y축 회전 및 애니메이션까지 들어가 있습니다.
이건 초창기 3D 게임에서나 보던 답답한 회전 조작 방식이네요.
키보드 & 마우스 조작 기준으로 방향키(WASD)는 앞뒤좌우 이동, 시점은 마우스 이동에 따라 회전하도록 하는 것이 보편적인 조작이지요.
최근 다크소울3를 해보았으니 이를 관찰하여 비슷한 느낌이 나도록 카메라 이동, 회전을 구현해보겠습니다.
우선 관찰한 결과부터 정리해 보겠습니다.
1) 마우스 좌우 이동 - 카메라가 캐릭터 머리 약간 위를 중심으로 좌우 이동에 따라 일정 반경으로 이동하면서
캐릭터 방향을 바라보도록 회전
좌우 이동으로 변경되는 카메라가 보고 있는 방향에 맞게 캐릭터 조작 좌표계 회전
2) 마우스 상하 이동 - 상하 이동에 따라 카메라 위아래 이동, 상하 이동 제한 값이 존재
3) 카메라 리셋 키 입력 - 캐릭터 등 뒤에서 앞쪽을 바라보도록 카메라를 빠르게 이동
4) 높은 높이에서 낙하할 경우 캐릭터와 멀어짐 - 착지하면 원래 시점으로 빠르게 복귀
아래는 해당 기능을 구현하기 위한 복사한 ThirdPersonCamera 스크립트의 수정본 전문입니다.
using UnityEngine;
namespace UnityChan
{
[RequireComponent(typeof(Camera))]
public class ThirdPersonCamera_Custom : MonoBehaviour
{
// 유니티짱 트랜스폼
Transform UnityChan;
// 유니티짱 컨트롤 스크립트의 클래스
UnityChanControlScriptWithRgidBody_Custom UnityChanControl;
// 카메라 초기화 시 목표 위치, 회전 각도로 할 유니티짱 자식 오브젝트
// 유니티짱을 기준으로 항상 동일한 로컬 위치
// (등 뒤 약간 위에서 유니티짱 정면을 약간 내려다보도록)
Transform initPos;
// 카메라와 플레이어의 이격 좌표
Vector3 positionDistance;
// 카메라 현재 회전 값
Vector3 rotation_temp;
// 시점 원점 이동
// 원점 이동 시 카메라 이동 속도
float originSpeed = 20f;
// 원점 이동 기능 off 카운터
float count = 0f;
// 원점 이동 기능 지속 시간
const float bQuickSwitch_toggle_delay = 1f;
// 마우스 조작으로 인한 시점 이동
// 카메라 원점 이동 기능 on/off
bool isCameraInit = false;
// 카메라 회전 값
float xRotate, yRotate;
// 마우스 이동 초기화 값
float xRotateOrigin, yRotateOrigin;
// 마우스 좌우 이동에 따른 카메라 공전 지름
float diameter_LR;
// 마우스 이동에 따른 카메라 위아래, 좌우 회전 속도 !!!!! 시점 이동 민감도 설정에서 조절할 값
float[] rotateSpeed = new float[2] { 2f, 5f };
// 위, 아래 시점 이동 상하한
float[] UpDownRotateClamp = new float[2] { -20f, 20f };
// 낙하 시점 및 복구
// 시점이 낙하 중인 캐릭터로부터 점점 멀어지는 느낌을 주어 낙하의 몰입감 증가
// 낙하 도중 Y좌표의 증가값
const float add_PosY_during_fall = 0.02f;
// 착지부터 시점 원상 복구에 들어가는 값(0~1사이의 수치만 적용)
float fallToNormal = 1f;
float fallToNormal_Speed;
// 게임 화면이 나온 뒤 1번만 수행할 구문을 구분
bool once;
// 카메라 위치 초기화 키 !!!!! 조작 설정에서 조정하게 추후엔 묶어서 관리
KeyCode cameraInit = KeyCode.Q;
void Start()
{
// 카메라의 원점으로 할 유니티짱 자식 오브젝트
initPos = GameObject.Find("CamPos").transform;
// 유니티짱 트랜스폼, 컨트롤 스크립트
UnityChan = GameObject.Find("unitychan").transform;
UnityChanControl = UnityChan.GetComponent<UnityChanControlScriptWithRgidBody_Custom>();
// 원점 위치, 회전값과 동일하게
transform.position = initPos.position;
transform.forward = initPos.forward;
positionDistance = initPos.localPosition;
// 원점 위치를 토대로 회전 지름 값을 초기화
diameter_LR = Mathf.Abs(initPos.localPosition.z) * 2f;
// 원점으로 할 xRotate, yRotate 값 초기화
xRotateOrigin = initPos.eulerAngles.x;
yRotateOrigin = diameter_LR;
rotation_temp = transform.eulerAngles;
// 낙하 후 시점 복원 속도 설정
fallToNormal_Speed = 3f * Time.deltaTime;
once = true;
}
private void Update()
{
// 게임 로딩이 끝나고 마우스 값이 튀는 것을 잡아주기 위해 1번만
if (once)
{
// 마우스 현재 위치를 원점으로
xRotate = xRotateOrigin;
yRotate = yRotateOrigin;
once = false;
}
// 카메라 리셋 키를 누르면 기능 on
if (Input.GetKeyDown(cameraInit))
isCameraInit = true;
// 카메라 리셋 중이면
if (isCameraInit)
{
// 원점으로 카메라 이동, 회전
transform.position = Vector3.Slerp(transform.position, initPos.position,
Time.deltaTime * originSpeed);
transform.forward = Vector3.Lerp(transform.forward, initPos.forward,
Time.deltaTime * originSpeed);
// 기능 켠 후 잠시 후 끄기(그 사이 원점으로 이동)
count += Time.fixedDeltaTime;
if (count > bQuickSwitch_toggle_delay)
{
count = 0f;
isCameraInit = false;
// 원점 리셋이 끝날 때, 1번만 리셋할 파라미터
initMouseOrigin();
}
}
// 카메라 리셋 중이 아닐 때는,
else
CameraMove();
}
// 현재 마우스 위치를 0점으로 조정
void initMouseOrigin()
{
// 유니티짱의 기준 각도를 0점으로 잡은 각도로 리셋
UnityChanControl.currentDirection = UnityChan.eulerAngles.y;
// 원점과 유니티짱의 떨어진 위치값을 리셋(회전값에 따라 달라진다)
positionDistance = initPos.position - UnityChan.position;
// 원점의 회전 값으로 리셋
rotation_temp = initPos.eulerAngles;
// 리셋 후 positionDistance의 위치, initPos의 상하 회전값에 맞게 마우스 위치 리셋
if (positionDistance.z >= 0f)
yRotateOrigin = positionDistance.x;
else if (positionDistance.x >= 0f)
yRotateOrigin = -positionDistance.x + diameter_LR;
else
yRotateOrigin = -positionDistance.x - diameter_LR;
xRotate = xRotateOrigin;
yRotate = yRotateOrigin;
}
// 플레이 도중 카메라 무브
void CameraMove()
{
// 유니티짱의 위치를 따라 이동하도록 위치 갱신
transform.position = UnityChan.position + positionDistance;
// 마우스 좌우 이동에 따라 캐릭터 주변을 공전 (After)
yRotate += Input.GetAxis("Mouse X") * rotateSpeed[1];
transform.RotateAround(UnityChan.position + Vector3.up * initPos.position.y,
Vector3.up, yRotate);
// 좌우 회전에 맞춰 유니티짱을 바라보게
transform.LookAt(UnityChan.position);
// 현재의 회전 값을 저장
rotation_temp = transform.eulerAngles;
// 낙하 중, 카메라와 약간씩 멀어지게
if (UnityChanControl.currentBaseState.fullPathHash == UnityChanControl.fallState
&& UnityChanControl.isFarFromFloor)
{
fallToNormal = 0f;
positionDistance.y += add_PosY_during_fall;
}
else
{
// 낙하 시점이었다면, 시점을 원래대로
if (fallToNormal < 1f)
{
fallToNormal += fallToNormal_Speed;
positionDistance.y = Mathf.Lerp(positionDistance.y, initPos.localPosition.y,
fallToNormal);
transform.eulerAngles = new Vector3(Mathf.Lerp(transform.eulerAngles.x, xRotate,
fallToNormal), rotation_temp.y, rotation_temp.z);
}
// 낙하 중에는 마우스 앞뒤 이동으로 인한 시점 전환을 막는다.
// 낙하가 끝나면 마우스 전후 이동에 대한 시점 상하 조작이 가능하게끔
else
{
// 마우스 앞뒤 이동에 따라 위아래 시점 이동값 산출
xRotate += -Input.GetAxis("Mouse Y") * rotateSpeed[0];
// 위, 아래 상한 각 고정
xRotate = Mathf.Clamp(xRotate, UpDownRotateClamp[0], UpDownRotateClamp[1]);
// 마우스 상하 이동에 대해 시점 회전
transform.eulerAngles = new Vector3(xRotate, rotation_temp.y, rotation_temp.z);
}
}
// 유니티짱의 이동좌표계 정면 각도를 카메라 y회전에 맞게 변화
UnityChanControl.currentDirection = rotation_temp.y;
}
}
}
// 아래는 시행착오 내용 정리
// [1] 일반 유니티짱으로 변경
// 유니티짱 다이나믹을 처음에 쓰려했으나 모션에 따른 머리 떨림은 좀 잡았으나
// 옷이 펄럭이며 몸을 파고드는 것은 잡지 못함
//UnityChan = GameObject.Find("unitychan_dynamic_locomotion").transform;
//UnityChanControl = UnityChan.GetComponent<UnityChanControlScriptWithRgidBody_Custom>();
// [2] 마우스 좌우 이동에 따라 캐릭터 주변을 공전 (Before)
// 실컷 수식으로 구현했으나 간단히 유니티 내에 제공하는 api가 있어 수정 ㅠㅠ
// float radius_LR = Mathf.Abs(initPos.localPosition.z);
// float radiusSquare_LR = radius_LR * radius_LR;
//yRotate += Input.GetAxis("Mouse X") * Time.deltaTime * rotateSpeed[1];
//if (yRotate <= radius_LR && yRotate >= -radius_LR)
// positionDistance = new Vector3(yRotate, initPos.position.y,
// Mathf.Sqrt(radiusSquare_LR - Mathf.Pow(yRotate, 2f)));
//else if (yRotate <= diameter_LR && yRotate > radius_LR)
// positionDistance = new Vector3(diameter_LR - yRotate, initPos.position.y,
// -Mathf.Sqrt(radiusSquare_LR - Mathf.Pow(diameter_LR - yRotate, 2f)));
//else if (yRotate < -radius_LR && yRotate >= -diameter_LR)
// positionDistance = new Vector3(-yRotate - diameter_LR, initPos.position.y,
// -Mathf.Sqrt(radiusSquare_LR - Mathf.Pow(-diameter_LR - yRotate, 2f)));
//else if (yRotate < 0f)
// yRotate = diameter_LR;
//else
// yRotate = -diameter_LR;

기능이 잘 구현되었는지 확인해봅니다.

낙하 시점 테스트

마우스 좌우 이동 -> 마우스 상하 이동 -> 카메라가 보는 방향과 유니티짱 이동 좌표계 전진 방향이 같은지 확인
-> 유니티짱 전진 중 마우스 좌우 이동으로 시점, 이동 좌표계가 함께 회전하는지 확인

카메라 리셋 키 입력에 대한 동작
gif로 따면서 약간 버벅거리는 모습이 보이나 실제 게임 화면에서는 좀 더 부드럽게 회전합니다.
'Unity 3D > 유니티 짱' 카테고리의 다른 글
| 유니티 짱 06) 다른 모델링 구하기 & 간단한 카툰 텍스쳐 변경 (0) | 2023.04.06 |
|---|---|
| 유니티 짱 05) 3D 액션 게임 캐릭터 조작 - 1 (0) | 2023.04.01 |
| 유니티 짱 03) 작업에 들어가기 전 씬, 스크립트 복제 (0) | 2023.03.29 |
| 유니티 짱 02) 에셋에는 무엇이 포함되어 있는가? (0) | 2023.03.20 |
| 유니티 짱 01) 에셋 불러오기 및 툰 셰이더 적용 (3D ver1.4.0) (0) | 2023.03.04 |