首页 > 解决方案 > 在 Unity 2D 中使用物理冲撞斜坡

问题描述

我正在 Unity 中开发一个 2D 项目。

角色控制器是基于物理的,所以我使用刚体来移动玩家。一切工作正常,除非我尝试对角色应用高速运动,如破折号。

这就是代码的样子。

我只是检查玩家是否在冲刺,所以我将 Vector2 运动增加了一定量。

    private void DashMovement() {
        if (isDashing) {
            movement.x *= dashFactor;
        }
    }

我也在计算地面角度,所以我将运动矢量设置为跟随地面倾斜度。

     private void OnSlopeMovement() {
        if (isGrounded && !isJumping) {
            float moveDistance = Mathf.Abs(movement.x);
            float horizontalOnSlope = Mathf.Cos(groundAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign(movement.x);
            float verticalOnSlope = Mathf.Sin(groundAngle * Mathf.Deg2Rad) * moveDistance;

            if (horizontalOnSlope != 0)
                movement.x = horizontalOnSlope;
            if (isGrounded && verticalOnSlope != 0)
                movement.y = verticalOnSlope;
        }

        SetMaxFallVelocity();
    }

所以我设置了刚体速度以使其移动。

    private void Move() {
        movement.x *= Time.fixedDeltaTime;
        if(isGrounded && !isJumping) movement.y *= Time.fixedDeltaTime;
        Vector3 targetVelocity = new Vector2(movement.x, movement.y);

        PlayerController.rb2d.velocity = Vector3.SmoothDamp(PlayerController.rb2d.velocity, targetVelocity, ref velocity, movementSmoothing);
    }

当我应用足够高的速度时,就会出现问题。我知道这个问题是因为物理学。我认为检查地面并用于计算 groundAngle 的射线工作速度不够快,无法跟踪该运动,因此我无法将玩家固定在地面上。

我想找到一个解决方案,而不会使玩家运动,或在斜坡上停止冲刺。

这就是它在游戏中的样子。

这就是刚体运动如何保持在地面上,跟随斜坡角度。

编辑:

这就是我获得地面角度的方式:

private void GroundAngle() {
        Vector2 rayOrigin = feetCollider.bounds.center;
        rayOrigin.y += 0.1f;
        Vector2 rayDirection = (Input.GetAxisRaw("Horizontal") == 0) ? Vector2.right : new Vector2(Input.GetAxisRaw("Horizontal"), 0);

        int groundCollisions = Physics2D.RaycastNonAlloc(rayOrigin, Vector2.down, groundResults, Mathf.Infinity, groundMask);
        if (groundCollisions > 0) {
            groundAngle = Vector2.Angle(groundResults[0].normal, rayDirection) - 90f;
            //Debug.DrawRay(rayOrigin, Vector2.down, Color.green);
            if (groundAngle > 0 && !isDashing) {
                rayOrigin.x += Input.GetAxisRaw("Horizontal") * .125f;
                Physics2D.RaycastNonAlloc(rayOrigin, Vector2.down, groundResults, Mathf.Infinity, groundMask);
                groundAngle = Vector2.Angle(groundResults[0].normal, rayDirection) - 90f;
                //Debug.DrawRay(rayOrigin, Vector2.down, Color.blue);
            }
        } 
    }

感谢@Ruzhim 的帮助。我只是为这个问题发布了第一个“解决方案”。根据 Ruzhim 的建议,我以这种方式使用了他的代码。

private void SetPositionAfterTick() {
        if (isDashMovement) {
            Vector2 currentPosition = new Vector2(transform.position.x, transform.position.y);
            currentPosition.y = feetCollider.bounds.min.y;
            Vector2 feetPosAfterTick = currentPosition + PlayerController.rb2d.velocity * Time.deltaTime;

            float maxFloorCheckDist = .1f;

            RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(feetPosAfterTick + Vector2.up * maxFloorCheckDist, Vector2.down, maxFloorCheckDist * 5f);

            if (groundCheckAfterTick) {
                Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;

                if (wantedFeetPosAfterTick != feetPosAfterTick) {
                    //PlayerController.rb2d.transform.position = (wantedFeetPosAfterTick + new Vector2(0f, feetCollider.bounds.min.y - PlayerController.rb2d.position.y));
                    PlayerController.rb2d.velocity = Vector2.zero;
                }
            }
        }
    }

这就是它的样子。

这足以继续完善该机制。我仍然需要以某种方式设置位置。刚体的位置计算不起作用,因为它现在被抬起,因为条件 (wantedFeetPosAfterTick != feetPosAfterTick) 始终为真,所以角色会摔倒地板并摔倒。

正如你所看到的,我还需要控制下坡运动,因为它有时会使用斜坡运动,而其他人则向前冲刺。

标签: c#unity3d

解决方案


这就是asker Rubzero实现以下代码为他们工作的方式:

private void SetPositionAfterTick() {
    if (isDashMovement) {
        Vector2 currentPosition = new Vector2(transform.position.x, transform.position.y);
        currentPosition.y = feetCollider.bounds.min.y;
        Vector2 feetPosAfterTick = currentPosition + PlayerController.rb2d.velocity * Time.deltaTime;

        float maxFloorCheckDist = .1f;

        RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(feetPosAfterTick + Vector2.up * maxFloorCheckDist,
                Vector2.down, maxFloorCheckDist * 5f);

        if (groundCheckAfterTick) {
            Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;

            if (wantedFeetPosAfterTick != feetPosAfterTick) {
                //PlayerController.rb2d.transform.position = (wantedFeetPosAfterTick + new Vector2(0f, feetCollider.bounds.min.y -
                        PlayerController.rb2d.position.y));
                PlayerController.rb2d.velocity = Vector2.zero;
            }
        }
    }
} 

这就是它的样子。

这足以继续完善该机制。我仍然需要以某种方式设置位置。刚体的位置计算不起作用,因为它现在被抬起,因为条件 (wantedFeetPosAfterTick != feetPosAfterTick)始终为真,所以角色会摔倒地板并摔倒。

正如你所看到的,我需要控制下坡运动,因为它有时会使用斜坡运动,而其他人则向前冲刺。


我同意 AresCaelum 的观点;如果您不想在完成上/下坡时保持动量,则使用物理学进行坡度运动与您想要做的几乎相反。具体来说,您的问题在这里:

float moveDistance = Mathf.Abs(movement.x);
float horizontalOnSlope = Mathf.Cos(groundAngle * Mathf.Deg2Rad) * moveDistance * Mathf.Sign(movement.x);
float verticalOnSlope = Mathf.Sin(groundAngle * Mathf.Deg2Rad) * moveDistance;

这是一个问题,因为玩家在一帧中水平移动的次数越多,根据他们所在坡道的斜率,他们垂直移动的次数就越多。然而,如果他们应该只在帧期间的部分移动期间沿着坡道行进,则此假设不成立。因此,您需要一种方法来处理这种情况。

一种解决方案是使用来自玩家所在位置的光线投射,然后如果它在地板上方,则更改垂直速度,以便将它们放置在该地板的位置。

首先,确定是否在物理框架中发生了斜坡运动......

 private bool slopeMovementOccurred = false;

 void FixedUpdate() {
     slopeMovementOccurred = false;

     // ...
 }

 private void OnSlopeMovement() {
    if (isGrounded && !isJumping) {
        slopeMovementOccurred = true; 

        // ...
    }

    SetMaxFallVelocity();
}

...如果有,请确定物理更新后玩家的去向。然后从该位置上方(一定量)向下(前一个量的两倍)进行physics2d raycast 以找到玩家的位置应该在哪里,然后更改rb2d.velocity它以将玩家准确放置在他们应该在的高度。

假设您可以计算出某种Vector2 feetOffset具有玩家脚部局部位置的位置:

void FixedUpdate() {
    // ...

    StickToSlopeLanding();
}

void StickToSlopeLanding() {
    if (slopeMovementOccurred) {
        Vector2 curVelocity = PlayerController.rb2d.velocity;
        Vector2 feetPosAfterTick = PlayerController.transform.position 
                + PlayerController.feetOffset 
                + curVelocity * Time.deltaTime;

        float maxFloorCheckDist = 1.0f;

        // determine where the player should "land" after this frame
        RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(
                feetPosAfterTick + Vector2.up * maxFloorCheckDist, 
                -Vector2.up, maxFloorCheckDist * 2f);

        if (groundCheckAfterTick.collider != null) {
            Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;

            // if basic physics won't take them to landing position
            if (wantedFeetPosAfterTick != feetPosAfterTick) {
                Vector2 wantedVelocity = curVelocity 
                        + Vector2.up 
                        * ((wantedFeetPosAfterTick.y - feetPosAfterTick.y)
                            / Time.deltaTime);

                // adjust velocity so that physics will take them to landing position
                PlayerController.rb2d.velocity = wantedVelocity;

                // optionally, set a flag so that next frame 
                // it knows the player should be grounded
            }
        }
    }
} 

希望这能让您找到可行的解决方案。


注意:您可能还需要移动刚体,使其不会尝试穿过坡道顶部的拐角,并且您可以使用另一个光线投射确定将刚体放置在哪里,将来自该点的速度设置为水平的:

void StickToSlopeLanding() {
    if (slopeMovementOccurred) {
        Vector2 curVelocity = PlayerController.rb2d.velocity;
        Vector2 feetPosAfterTick = PlayerController.transform.position 
                + PlayerController.feetOffset 
                + curVelocity * Time.deltaTime;

        float maxFloorCheckDist = 1.0f;

        // determine where the player should "land" after this frame
        RaycastHit2D groundCheckAfterTick = Physics2D.Raycast(
                feetPosAfterTick + Vector2.up * maxFloorCheckDist, 
                -Vector2.up, maxFloorCheckDist * 2f);

        if (groundCheckAfterTick.collider != null) {
            Vector2 wantedFeetPosAfterTick = groundCheckAfterTick.point;

            // if basic physics won't take them to landing position
            if (wantedFeetPosAfterTick != feetPosAfterTick) {

                // look for corner of ramp+landing. 
                // Offsets ensure we don't raycast from inside/above it
                float floorCheckOffsetHeight = 0.01f;
                float floorCheckOffsetWidth = 0.5f;
                RaycastHit2D rampCornerCheck = Physics2D.Raycast(
                        wantedFeetPosAfterTick 
                        - floorCheckOffsetHeight * Vector2.up 
                        - floorCheckOffsetWidth * Mathf.Sign(movement.x) * Vector2.right,
                        Mathf.Sign(movement.x) * Vector2.right);

                if (rampCornerCheck.collider != null) {
                    // put feet at x=corner position
                    Vector2 cornerPos = Vector2(rampCornerCheck.point.x, 
                            wantedFeetPosAfterTick.y);

                    PlayerController.rb2d.position = cornerPos 
                            - PlayerController.feetOffset;
                    // adjust velocity so that physics will take them from corner 
                    // to landing position
                    Vector2 wantedVelocity = (wantedFeetPosAfterTick - cornerPos) 
                            / Time.deltaTime;

                    PlayerController.rb2d.velocity = wantedVelocity;

                    // optionally, set a flag so that next frame 
                    // it knows the player should be grounded
                }
            }
        }
    }
} 

推荐阅读