Unity를 사용하여 플레이어 움직임을 3D 오브젝트의 표면으로 제한하려면 어떻게해야합니까?


16

마리오 갤럭시 또는 Geometry Wars 3과 유사한 효과를 만들려고합니다. 플레이어가 "행성"중력을 따라 움직일 때 중력이 조정되는 것처럼 물체의 가장자리에서 떨어지지 않습니다. 한 방향으로 고정되었습니다.

여기에 이미지 설명을 입력하십시오
(출처 : gameskinny.com )

기하학 전쟁 3

나는 중력을 가져야하는 물체가 다른 강체를 끌어 당기는 접근법을 사용하여 찾고자하는 것에 가까운 것을 구현했지만 Unity에 내장 된 물리 엔진을 사용하고 AddForce 등으로 움직임을 적용하여 나는 운동이 옳은 느낌을 갖지 못했습니다. 나는 플레이어가 물체의 표면에서 날아 가기 시작하지 않고 플레이어를 충분히 빨리 움직일 수 없었으며, 이것을 수용하기 위해 가해진 힘과 중력의 균형을 찾지 못했습니다. 내 현재 구현은 여기 에서 찾은 내용의 적응입니다.

해결책은 아마도 물리를 사용하여 플레이어가 표면을 떠날 경우 물체에 접지되게하는 것처럼 느껴지지만 플레이어가 접지되면 플레이어를 표면에 물리고 물리를 끄는 방법이 있습니다. 다른 방법으로 플레이어를 제어하지만 확실하지 않습니다.

플레이어를 물체의 표면에 끼 우려면 어떤 종류의 접근 방식을 취해야합니까? 이 솔루션은 3D 공간 (2D가 아닌)에서 작동해야하며 무료 버전의 Unity를 사용하여 구현할 수 있어야합니다.



벽을 걸을 생각조차하지 않았습니다. 한 번 살펴보고 도움이되는지 살펴 보겠습니다.
SpartanDonut

1
이 질문이 Unity에서 3D로이 작업을 수행하는 것에 관한 것이라면 편집을 통해 더 명확하게해야합니다. (그 당시의 기존 복제본과 정확히 일치하지는 않습니다 .)
Anko

그것은 저의 일반적인 느낌이기도합니다. 나는 그 솔루션을 3D에 적용하고 그것을 해답으로 게시 할 수 있는지 알아볼 것입니다. 더 명확하게하기 위해 내 질문을 업데이트하려고합니다.
SpartanDonut

잠재적으로 도움이되는 내용 : youtube.com/watch?v=JMWnufriQx4
ssb

답변:


7

나는 퍼즐의 표면 스냅 조각에 대한 이 블로그 게시물 의 도움으로 필요한 것을 달성 했으며 플레이어 움직임과 카메라에 대한 내 자신의 아이디어를 생각해 냈습니다.

플레이어를 객체 표면에 물리기

기본 설정은 큰 구체 (세계)와 작은 구체 (플레이어)로 구성되어 있으며 구체 구체가 부착되어 있습니다.

수행되는 대부분의 작업은 다음 두 가지 방법으로 이루어졌습니다.

private void UpdatePlayerTransform(Vector3 movementDirection)
{                
    RaycastHit hitInfo;

    if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
    {
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
        Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);

        transform.rotation = finalRotation;
        transform.position = hitInfo.point + hitInfo.normal * .5f;
    }
}

private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
    Vector3 newPosition = transform.position;
    Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);        

    if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
    {
        return true;
    }

    return false;
}

Vector3 movementDirection나 처음에 파악하기 위해 매개 변수는 소리대로, 우리는이 예에서는 비교적 간단한 결국하면서,이 프레임에 우리의 플레이어를 이동하고, 그 벡터를 계산하는 가고있는 방향은 조금 까다했다. 나중에 더 자세히 설명하지만 플레이어 가이 프레임을 움직이는 방향으로 표준화 된 벡터라는 점을 명심하십시오.

먼저, 우리가하는 첫 번째 일은 플레이어 다운 벡터 (-transform.up)를 향한 가상의 미래 위치에서 발생하는 광선이 스크립트의 공용 LayerMask 속성 인 WorldLayerMask를 사용하여 세계에 닿는 지 확인하는 것입니다. 더 복잡한 충돌이나 여러 레이어를 원한다면 자신 만의 레이어 마스크를 만들어야합니다. 레이 캐스트가 성공적으로 충돌하면 hitInfo를 사용하여 오브젝트의 올바른 위치와 회전을 계산하기 위해 정상 및 적중 지점을 검색합니다. 문제의 플레이어 객체의 크기와 원점에 따라 플레이어의 위치를 ​​상쇄해야 할 수도 있습니다.

마지막으로, 이것은 실제로 테스트되었으며 구체와 같은 간단한 물체에서만 잘 작동합니다. 내가 제안한 솔루션을 기반으로 한 블로그 게시물에서 여러 복잡한 레이 캐스트를 수행하고 위치와 회전에 대한 평균을 계산하여 더 복잡한 지형을 이동할 때 훨씬 더 좋은 전환을 얻을 수 있습니다. 이 시점에서 내가 생각하지 않은 다른 함정이있을 수도 있습니다.

카메라와 움직임

플레이어가 물체의 표면에 달라 붙으면 다음으로해야 할 일은 움직임이었습니다. 나는 원래 플레이어를 상대로 움직임을 시작했지만 구 방향에서 갑자기 방향이 바뀌면서 플레이어가 빠르게 방향을 바꾸고 극을 통과하지 못하게하는 문제가 발생하기 시작했습니다. 내가 상처를 준 것은 플레이어들이 카메라를 기준으로 움직이게하는 것이 었습니다.

내 필요에 잘 맞는 것은 플레이어 위치만을 기준으로 플레이어를 엄격하게 따르는 카메라를 갖추는 것이 었습니다. 결과적으로, 카메라가 기술적으로 회전하고 있더라도 위를 누르면 플레이어가 항상 화면의 상단을 향하고 하단을 향하는 방향으로 좌우로 이동합니다.

이를 위해 대상 객체가 플레이어 인 카메라에서 다음이 실행되었습니다.

private void FixedUpdate()
{
    // Calculate and set camera position
    Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
    this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);

    // Calculate and set camera rotation
    Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}

마지막으로 플레이어를 움직이기 위해 메인 카메라의 변형을 활용하여 컨트롤을 위로, 아래로 아래로 이동하는 등의 작업을 수행했습니다. 그리고 여기에서는 UpdatePlayerTransform을 호출하여 위치를 월드 오브젝트에 고정시킵니다.

void Update () 
{        
    Vector3 movementDirection = Vector3.zero;
    if (Input.GetAxisRaw("Vertical") > 0)
    {
        movementDirection += cameraTransform.up;
    }
    else if (Input.GetAxisRaw("Vertical") < 0)
    {
        movementDirection += -cameraTransform.up;
    }

    if (Input.GetAxisRaw("Horizontal") > 0)
    {
        movementDirection += cameraTransform.right;
    }
    else if (Input.GetAxisRaw("Horizontal") < 0)
    {
        movementDirection += -cameraTransform.right;
    }

    movementDirection.Normalize();

    UpdatePlayerTransform(movementDirection);
}

보다 흥미로운 카메라를 구현하지만 컨트롤이 여기에있는 것과 거의 동일하게 렌더링하기 위해 렌더링되지 않은 카메라 또는 움직임을 기반으로하는 다른 더미 오브젝트를 쉽게 구현 한 다음 더 흥미로운 카메라를 사용하여 당신은 게임처럼 보이기를 원합니다. 이를 통해 컨트롤을 깨지 않고도 객체를 둘러 볼 때 멋진 카메라 전환이 가능합니다.


3

이 아이디어가 효과가 있다고 생각합니다.

플래닛 메쉬의 CPU 쪽 사본을 유지하십시오. 메쉬 정점을 가짐으로써 지구상의 각 점에 대해 법선 벡터가 있음을 의미합니다. 그런 다음 모든 개체에 대해 중력을 완전히 비활성화하고 법선 벡터의 반대 방향으로 힘을가하십시오.

자, 행성의 법선 벡터를 어느 지점에서 계산해야합니까?

가장 쉬운 대답은 (제대로 작동 할 것입니다) 뉴턴의 방법 과 비슷합니다 . 물체가 처음 스폰되면 지구상의 모든 초기 위치를 알 수 있습니다. 초기 위치를 사용하여 각 객체의 up벡터 를 결정하십시오 . 분명히 중력은 반대 방향 (쪽으로 down)이됩니다. 다음 프레임에서는 중력을 적용하기 전에 객체의 새 위치에서 오래된 down벡터를 향해 광선을 투사합니다 . 그 광선과 행성의 교차점을 up벡터 를 결정하기위한 새로운 기준으로 사용하십시오 . 드문 경우지만 광선이 닿는 것은 무언가 잘못되었다는 것을 의미하므로 개체를 이전 프레임의 원래 위치로 다시 이동해야합니다.

또한이 방법을 사용하면 플레이어 원점이 행성에서 멀어 질수록 근사치가 더 나빠집니다. 따라서 각 플레이어의 발 주위를 원점으로 사용하는 것이 좋습니다. 나는 추측하지만 피트를 원점으로 사용하면 플레이어를 쉽게 다루고 탐색 할 수 있다고 생각합니다.


마지막 참고 : 더 나은 결과를 위해 다음을 수행 할 수도 있습니다. 각 프레임에서 플레이어의 움직임을 추적합니다 (예 :) current_position - last_position. 그런 다음 movement_vector물체를 향한 길이 up가 0 이 되도록 고정하십시오 . 이 새로운 벡터라고합시다 reference_movement. 이전 기준 reference_point을 이동 reference_movement하고이 새로운 점을 광선 추적 원점으로 사용하십시오. 광선이 행성에 닿은 reference_point후 해당 적중 지점으로 이동하십시오 . 마지막 up으로이 new에서 새로운 벡터를 계산하십시오 reference_point.

요약 할 의사 코드 :

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}

2

이 게시물 이 도움 될 수 있습니다. 요점은 캐릭터 컨트롤러를 사용하지 않고 물리 엔진을 사용하여 직접 만드는 것입니다. 그런 다음 플레이어 아래에서 감지 된 법선을 사용하여 법선 표면으로 향하게합니다.

이 기술에 대한 좋은 개요 가 있습니다. "3d 객체 마리오 갤럭시에서 유니티 워크"와 같은 웹 검색 용어를 사용하면 더 많은 리소스가 있습니다.

군인과 개가 있고 장면 중 하나에 워킹 엔진이있는 Unity 3.x의 데모 프로젝트도 있습니다. 갤럭시 스타일의 3D 객체를 걷는 것을 시연했습니다 . 그것은 runevision에 의해 운동 시스템 이라고 불 립니다.


1
언뜻보기에는 데모가 잘 작동하지 않으며 Unity 패키지에 빌드 오류가 있습니다. 이 작업을 수행 할 수 있는지 확인하지만보다 완전한 답변을 부탁드립니다.
SpartanDonut

2

회전

answers.unity3d.com (실제로 요청) 에서이 질문에 대한 답변을 확인 하십시오 . 인용문:

첫 번째 과제는 정의하는 벡터를 얻는 것입니다. 그림에서 두 가지 방법 중 하나로 할 수 있습니다. 행성을 구체로 취급하고 (object.position-planet.position)을 사용할 수 있습니다. 두 번째 방법은 Collider.Raycast ()를 사용하고 반환하는 'hit.normal'을 사용하는 것입니다.

그가 제안한 코드는 다음과 같습니다.

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

모든 업데이트를 호출하면 제대로 작동해야합니다. (코드는 UnityScript에 있습니다 ). C #
의 동일한 코드 :

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

중량

중력을 위해, 당신은 내 질문에 설명 된 나의 "행성 물리학 기술"을 사용할 수 있지만 실제로 최적화되지는 않았습니다. 그것은 단지 내가 생각했던 것입니다.
나는 중력에 대한 자신의 시스템을 만드는 것이 좋습니다.

다음은 Sebastian Lague의 Youtube 튜토리얼 입니다. 그것은 매우 잘 작동하는 솔루션입니다.

편집 : 단결에서 편집 > 프로젝트 설정 > 물리 로 이동 하여 모든 값을 설정하십시오 중력 을 0으로 하거나 모든 객체 에서 Rigidbody 를 제거하십시오 . 내장 중력 (플레이어를 끌어 내리는)을 방지하기 위해 맞춤형 솔루션. (끄기)


플레이어를 행성의 중심으로 끌어 들이고 그에 따라 회전 시키면 거의 구형의 행성에서 잘 작동하지만 로켓 모양의 마리오가 첫 번째 GIF에서 뛰어 오르는 것과 같이 구형이 아닌 물체에는 실제로 잘못되는 것을 상상할 수 있습니다.
Anko

비 구형 오브젝트 내부와 같이 X 축 주위에서만 이동하도록 제한된 큐브를 작성하고 플레이어가 움직일 때 이동하여 항상 플레이어 바로 아래에 있도록 플레이어를 당길 수 있습니다. 큐브까지. 시도 해봐.
Daniel Kvist
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.