1
\$\begingroup\$

i've been developing a third person adventure game on Unity with a grappling hook and i found a problem that i don't understand and it's driving me mad. Basically, if the player character collides strong with another object it won't stop moving without my control. Can you help me? Here's a gameplay video (the bug starts on 00:20): https://www.youtube.com/watch?v=2XAw-nIMSvQ&feature=youtu.be

Here's a screenshot of the inspector of the player character and the object that i hooked.

enter image description here

enter image description here

Here's the character controller code:

 using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ThirdPersonCharacterControl : MonoBehaviour
{
    public float Speed;
    //
    public Rigidbody rb;
    //

    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        PlayerMovement();
    }

    void PlayerMovement()
    {
        float hor = Input.GetAxis("Horizontal");
        float ver = Input.GetAxis("Vertical");
        Vector3 playerMovement = new Vector3(hor, 0f, ver) * Speed * Time.deltaTime;
        transform.Translate(playerMovement, Space.Self);
        //Debug.Log("Movimento");
    }

    ///detetor de colisões
    void OnCollisionEnter (Collision col)
    {
        if(col.gameObject.tag == "collidable")
        {
            Debug.Log("Tocou");
        }
    }
    ///
}

Here's the camera controller:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ThirdPersonCameraControl : MonoBehaviour
{
    float rotationSpeed = 1;
    public Transform Target, Player;
    float mouseX, mouseY;
    //aqui meto os obstructions (onde podes fazer grappling hook)
    public Transform Obstruction1;
    public Transform Obstruction2;
    public Transform Obstruction3;
    /// 
    float zoomSpeed = 2f;

    void Start()
    {
        Obstruction1 = Target;
        Obstruction2 = Target;
        Obstruction3 = Target;
        Cursor.visible = false;
        Cursor.lockState = CursorLockMode.Locked;

    }

    private void LateUpdate()
    {
        CamControl();
      //  ViewObstructed();
    }


    void CamControl()
    {
        mouseX += Input.GetAxis("Mouse X") * rotationSpeed;
        mouseY -= Input.GetAxis("Mouse Y") * rotationSpeed;
        mouseY = Mathf.Clamp(mouseY, -35, 60);

        transform.LookAt(Target);

        if (Input.GetKey(KeyCode.LeftShift))
        {
            Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
        }
        else
        {
            Target.rotation = Quaternion.Euler(mouseY, mouseX, 0);
            Player.rotation = Quaternion.Euler(0, mouseX, 0);
        }
    }
}

Here's the grappling hook code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GrapplingHook : MonoBehaviour
{
    public GameObject hook;
    public GameObject hookHolder;

    public float hookTravelSpeed;
    public float playerTravelSpeed;

    public bool jumping;

    public static bool fired;
    public bool hooked;
    public GameObject hookedObj1;

    public float maxDistance;
    private float currentDistance;

    public bool grounded;
    public Rigidbody rb; 
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    void Update()
    {
        //firing the hook
        if (Input.GetMouseButtonDown(1)/*Input.GetKeyDown(KeyCode.E)*/ && fired == false)
        {
            fired = true;
        }

        if (fired)
        {
            LineRenderer rope = hook.GetComponent<LineRenderer>();
            rope.SetVertexCount(2);
            rope.SetPosition(0, hookHolder.transform.position);
            rope.SetPosition(1, hook.transform.position);
        }

        if (fired == true && hooked == false)
        {
            hook.transform.Translate(Vector3.forward * Time.deltaTime * hookTravelSpeed);
            currentDistance = Vector3.Distance(transform.position, hook.transform.position);

            if (currentDistance >= maxDistance)
            {
                ReturnHook();
            }
        }

        if (hooked == true && fired == true)
        {
            hook.transform.parent = hookedObj1.transform;
            transform.position = Vector3.MoveTowards(transform.position,
            hook.transform.position, Time.deltaTime * playerTravelSpeed);
            float distanceToHook = Vector3.Distance(transform.position, hook.transform.position);

            this.GetComponent<Rigidbody>().useGravity = false;

            if (distanceToHook < 1)
            {
                if (grounded == false)
                {
                    this.transform.Translate(Vector3.forward * Time.deltaTime * 13f);
                    this.transform.Translate(Vector3.up * Time.deltaTime * 18f);
                }

                StartCoroutine("Climb");

            }
        }
        else
        {
            hook.transform.parent = hookHolder.transform;
            this.GetComponent<Rigidbody>().useGravity = true;
        }
        CheckIfGround();
        PlayerJump();


    }

    IEnumerator Climb()
    {
        yield return new WaitForSeconds(0.1f);
        rb.AddForce(new Vector3(0, 3, 0), ForceMode.Impulse);
        ReturnHook();
    }

    void ReturnHook()
    {
        hook.transform.rotation = hookHolder.transform.rotation;
        hook.transform.position = hookHolder.transform.position;
        fired = false;
        hooked = false;

        LineRenderer rope = hook.GetComponent<LineRenderer>();
        rope.SetVertexCount(0);
    }

    public void CheckIfGround()
    {
        RaycastHit hit;
        float distance = 1.1f;
        Vector3 dir = new Vector3(0, -1, 0);



        if (rb.velocity.y <= 0.1 && Physics.Raycast(transform.position, dir, out hit, distance))
        {
            grounded = true;
            jumping = false;



            //Debug.Log("Grounded");
        }
        else
        {

            grounded = false;
        }
    }
    void PlayerJump()
    {

        if (Input.GetKeyDown(KeyCode.Space) && grounded == true)
        {
            rb.AddForce(new Vector3(0, 20, 0), ForceMode.Impulse);
            jumping = true;
            //print("jumping");
        }
        else if (Input.GetKey(KeyCode.Space) && grounded == false && rb.velocity.y < -1)
        {
            this.GetComponent<Rigidbody>().useGravity = false;
        }
    }


}

And the hook detector:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HookDetector : MonoBehaviour
{
    public GameObject player;

   void OnTriggerEnter(Collider other)
   {
        if(other.tag == "Hookable")
        {
        player.GetComponent<GrapplingHook>().hooked = true;
        player.GetComponent<GrapplingHook>().hookedObj1 = other.gameObject;
        //Debug.Log("Tocou");
        }
   }
}
\$\endgroup\$
1
  • \$\begingroup\$ it's cool that you're making a grappling hook. :) \$\endgroup\$
    – Millard
    Commented Jan 27, 2020 at 18:41

1 Answer 1

1
\$\begingroup\$

Your character has a Rigidbody. That Rigidbody is dynamic (its isKinematic box is unchecked).

That means that every fixed timestep, the physics engine will integrate its motion using the Rigidbody's velocity property.

Because all of your player control is accomplished with transform.Translate, you're teleporting your character around without influencing the velocity of its Rigidbody. So the physics engine's idea of how your object is moving is completely disconnected from your idea of how the object is moving.

This is a common mistake that can lead to all kinds of problems like...

  • Judder & broken interpolation
  • Unrealistic collisions
  • Objects getting wedged into or tunneling through one another
  • Residual velocity moving objects in unwanted ways

That last point is the issue you're experiencing. Because you never zero-out the velocity when your character is supposed to be stationary, any leftover velocity the object had from recent collisions/etc. will keep nudging it around. transform.Translate({0,0,0}) just says "don't add any additional movement through the Transform in this line," not "stop all movement from other sources like physics"

The best thing you can do, if you want to control a dynamic Rigidbody, is to do so with the physics engine, using velocity or AddForce. This keeps the motion in the control of the physics engine, so you don't create the bugs described above, and ensures that your script's idea of how the object is moving matches the physics engine's idea.

So for example, your main ground control script could look like this (do it in FixedUpdate to avoid adding a frame of latency to your game):

Rigidbody body;

void PlayerMovement()
{
    float hor = Input.GetAxis("Horizontal");
    float ver = Input.GetAxis("Vertical");

    // No need for deltaTime here;
    // the physics engine will integrate over the timestep for us.
    Vector3 targetVelocity = new Vector3(hor, 0f, ver) * Speed;

    // Retain body's vertical velocity to keep jumping/falling normal.
    targetVelocty.y = body.velocity.y;

    // Instead of setting directly, you can accelerate toward this target
    // to play nicer with multiple scripts manipulating the same object.
    body.velocity = targetVelocity;
}

See a more detailed example of incorporating acceleration here.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ ...and if you don't want to use forces and prefer to take direct control of a rigidbody, at least use rigidbody.MovePosition instead of transform.Translate. That way you tell the physics engine that you intend to move the object, not teleport it without the physics engine knowing about it. \$\endgroup\$
    – Philipp
    Commented Oct 17, 2022 at 12:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .