객체가 경사로를 이동할때 위로 뿅하고 튀어오르는 현상이 있을거다. 분명 나는 인풋으로 x,z축 데이터만 넣었는데 왜 경사로에서 튀어오르는지 의문일 것이다.
y값을 보정으로 하고있어도 경사로의 콜라이더와 플레이어의 콜라이더가 충돌이 일어나 생기는 힘이기 때문에 위로 튀어오를수 밖에 없을것이다.
이럴떄는 경사도의 법선벡터를 이용하여 투영벡터를 계산해서 진행방향을 알아내야한다.
경사도를 오르는 객체의 아랫방향(Vector3.down)으로 ray를 쏴서 해당 경사면의 각도를 알아야한다.
p.s 경사도의 각도를 알아야하는 이유
경사면의 각도를 알아야 하는 이유는 이동오브젝트가 여기 경사면을 넘을수 있는지 없는지를 체크하기 위해서다.
즉, 완만한 경사면 오를수 있을것이고 급한경사는 못오르는 maxSlopeAngle 변수를 설정해서 컨트롤 해 줄수 있게 해야된다.
그러기위해서 경사면의 노말벡터(법선벡터,Slopehit.Normal) 과 윗 방향의 벡터(Vector3.up) 사이의 각도를 구한뒤에 넘어갈수 있는 각도인지 체크한다.
넘어 갈수 있게되면 해당 경사면의 진행 방향을 알아야된다.
ProjectOnPlane() 메서드를 사용하여 내가 이동하는 방향벡터 movediretion 과 경사도의 법선벡터 slopeHit.normal의 투영 벡터를 구해야만 해당 경사도에서의 이동 방향을 구 할수 있게된다.
그림으로 간단히 표현한 블로그가 있어 사진을 퍼왔다.
위의 설명한 이론을 아래 코드로 구현해보았다.
public class PlayerController : MonoBehaviour
{
private Rigidbody rigid;
public PlayerStatus status;
[Header("Slope Handling")] //경사도에 올라갈시 위로 솟아오르는 현상 제어
public float maxSlopeAngle; //해당 경사도 보다 작은경우 캐릭터가 올라갈수 있게 설정
private RaycastHit slopeHit; //캐릭터 밑 경사도의 법선벡터 호출용으로 사용되는 변수
[Header("Movement")]
private Vector2 curMoveInput;
private Vector3 moveDirection;
public Vector3 ExtraDir; //외부에서 힘을 가할떄 x,z축 이동방향값을 보정해주는 변수
[Header("IsGrounded")]
public float RayDistance;
public Transform GroundPivot;
public LayerMask GroundMask;
//y축 보정을 스크립트로 중력 추가 보정 함수
private void ApplyCustomGravity()
{
// 중력을 수동으로 추가하여 y축 낙하를 더 강하게 설정
Vector3 gravity = Physics.gravity * 3.0f;
rigid.AddForce(gravity, ForceMode.Acceleration);
}
//캐릭터의 이동을 담당하는 함수
public void Move()
{
//z축(forward)와 x축(right)방향 입력값을 받음
moveDirection = curMoveInput.y * transform.forward + curMoveInput.x * transform.right;
//점프를 하지 않는 이상 y축 값은 변동이 없기에 y축 보정
float velocity_Y = rigid.velocity.y;
//경사도 기울기 체크
if (OnSlope())
{
//경사로에 있기 때문에 경사로의 객체 속도 보정
moveDirection = GetSlopeMoveDirection();
//경사로에 진행방향에 맞춰 y축도 속도를 보정해준다.
velocity_Y = moveDirection.y * status.CurSpeed;
}
Vector3 moveVelocity = moveDirection * status.CurSpeed;
rigid.velocity = new Vector3(moveVelocity.x, velocity_Y, moveVelocity.z) ;
}
//플레이어 객체가 경사도에 있는지 체크 및 이동 가능한지 확인하는 함수
private bool OnSlope()
{
Ray ray = new Ray(GroundPivot.position, Vector3.down);
//0.3f는 경사도에 있기에 추가로 더 길게 쏜것
if (Physics.Raycast(ray, out slopeHit, 0.3f, GroundMask))
{
// Vector3.Angle : 두개의 벡터 사이의 각도값을 구하는 함수
/*해당 로직은 월드 y축(uP) 방향과 경사로의 법선벡터(slopehit) 사이의 각도를 받아온다.*/
float angle = Vector3.Angle(Vector3.up, slopeHit.normal);
return angle <= maxSlopeAngle && angle != 0;
}
return false;
}
//경사로에 있기 때문에 경사로의 객체 속도를 보정해주는 함수
private Vector3 GetSlopeMoveDirection()
{
//경사로에 있는 객체의 진행 방향을 알기위해서는 투영벡터를 사용해야된다.
//투영벡터를 사용하기 위해서 ProjectOnPlane() 메서드를 사용한다.
Vector3 reflectV = Vector3.ProjectOnPlane(moveDirection, slopeHit.normal).normalized;
if (reflectV != Vector3.zero)
{
Debug.Log(reflectV);
}
return reflectV;
}
}
해당 글을 작성하며 도움 받은 자료
https://www.youtube.com/watch?v=xCxSjgYTw9c&t=378s
https://code-piggy.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-Vector3ProjectOnPlane