Rigidbody and "Normal" movement result in either jittering movement or collisions

Hey,

i am currently working on a 3D snake game.
The code looks like this:

public class SnakeScript : MonoBehaviour
{
    //movement
    private Rigidbody rb;
    public float force;
    public float turnspeed;

    //spawning
    public int spawndelay;
    private GameObject activesegment;
    private GameObject activetail;
    private List<Vector3> lastpos = new List<Vector3>();
    private List<Quaternion> lastrotation = new List<Quaternion>();
    public List<GameObject> segments = new List<GameObject>();
    public float spawnthreshold;
    private bool spawninque = false;

    //graphic
    public int skin;
    public List<GameObject> heads = new List<GameObject>();
    public List<GameObject> bodys = new List<GameObject>();
    public List<GameObject> tails = new List<GameObject>();
    private bool hastail = false;
    private GameObject tail;



    private int i = 1;

    private void Start()
    {
        activetail = tails[skin];
        activesegment = bodys[skin];
        rb = GetComponent<Rigidbody>();
        GameObject body = Instantiate(heads[skin], transform.position, transform.rotation);
        body.transform.SetParent(this.gameObject.transform);

        //InvokeRepeating("addnewSegment", 3.0f, 2f);
    }

    private void Update()
    {
        if (spawninque)
        {
            addnewSegment();
            spawninque = false;
        }

        Move();

        lastpos.Insert(0, transform.position);
        lastrotation.Insert(0, transform.rotation);

        UpdateSegments();

        int index = (segments.Count+2) * spawndelay;
        if(index < lastpos.Count){
            lastpos.RemoveRange(index, lastpos.Count - index);
        }


    }

    public void Move()
    {
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            if (touch.position.x > Screen.width / 2)
            {
                print("Moveleft");
            }
            else
            {
                print("MoveRights");
            }
        }

        if (Input.GetMouseButton(0))
        {

            if (Input.mousePosition.x > Screen.width / 2)
            {
                //rechts
                rb.AddRelativeTorque(turnspeed * transform.up * Time.deltaTime, ForceMode.Force);

            }
            else
            {
                //Link
                rb.AddRelativeTorque(-turnspeed * transform.up * Time.deltaTime, ForceMode.Force);

            }
        }
        rb.angularVelocity = Vector3.Lerp(rb.angularVelocity, Vector3.zero, 0.1f);
        //rb.velocity = force * transform.forward * Time.deltaTime;
        transform.position += force * transform.forward;
        //rb.MovePosition(transform.position + force * transform.forward);
        //rb.position += force * transform.forward;
    }

    private void FixedUpdate()
    {
        //rb.velocity = force * transform.forward;

    }

    public void addnewSegment()
    {

        if (Vector3.Distance(transform.position, lastpos[spawndelay * i]) > spawnthreshold)
        {
            if (hastail)
            {
                GameObject newsegment = Instantiate(activesegment, lastpos[spawndelay * i], lastrotation[spawndelay * i]);
                segments.Add(newsegment);
                newsegment.GetComponent<SegmentScript>().spawnint = spawndelay * (i - 1);
                tail.GetComponent<SegmentScript>().spawnint += spawndelay;
                i++;
            }
            else
            {
                tail = Instantiate(activetail, lastpos[spawndelay * i], lastrotation[spawndelay * i]);
                segments.Add(tail);
                tail.GetComponent<SegmentScript>().spawnint = spawndelay * i;
                i++;
                hastail = true;
            }
           
        }
        else
        {
            spawninque = true;
        }
    }

    void UpdateSegments()
    {

        foreach (GameObject intsegment in segments)
        {
            intsegment.transform.position = lastpos[intsegment.GetComponent<SegmentScript>().spawnint];
            intsegment.transform.rotation = lastrotation[intsegment.GetComponent<SegmentScript>().spawnint];
        }
    }

}

so basically I move my object, then the position and rotation to a list and have all the snake segments follow that list.
The problem is that with using rb.velocity = force * transform.forward; I get jittery forward movement.
When using transform.position += force * transform.forward; I get jittery collisions. What am I doing wrong?
I´ve tried every combination of fixedupdate and normal with or without fixed delta time.

You shouldn’t be doing all that rigidbody stuff outside of FixedUpdate. Keep your input detection in Update though.

If you’re modifying the transform.position directly, you will probably want to raycast to know a collision will occur instead of waiting for the physics update to knock the object back after one occurs.

Is it really necessary to use physics for that?
I would suggest that you just move the snake via script.
You should also in most cases not mix transform.position (setting) in combination with physics.

1 Like

You need to split up your input and physics code. Always do physics code in FixedUpdate, and never do input code in FixedUpdate. The former is the more important one here.

Doing physics code in Update can have jittery results for different reasons, usually boiling down to one of two things:

  1. All of the AddForce/AddTorque type functions apply force across the duration of the “frame” in which they’re being called - a game frame in Update, or a physics frame in FixedUpdate. If they’re called in FixedUpdate, this duration is always the same value, so there’s no inconsistency. If they’re called in Update, this duration will change every time, so the amount of force can change every frame.
  2. physics updates may happen zero, one, or more times for each game frame. So as an example, if you set velocity = 4 each frame in Update, there will be an unpredictable number of physics updates which may be altering the velocity, e.g. via friction. If there are more physics frames, then friction will be taking a more dominant role in the object’s speed compared to you forcibly setting it to 4, so the average speed may be lower. That inconsistency is bad.

As for altering transform.position vs rigibody.velocity, choose one:
A) change transform.position manually always, and set your rigidbody to kinematic. Do this in Update. (This will make your snake steamroll any and all obstacles - effectively, not being affected by physics at all. Collisions with other moveable physics objects may cause unpredictable bounces.)
B) only ever set rigidbody.velocity, never touching the transform.position [except for things like teleportation, level reset, etc]. Do this in FixedUpdate (This will be affected by physics, e.g. will slide along walls properly. Other moveable objects will bounce in a predictable way off your snake. This is probably what you what.)

So, stick to setting anything to do with physics in FixedUpdate, and only ever setting your .velocity, and go from there. If you do just that, does that behave correctly, or is there still something left to fix?

On another note: Rather than List, you can store a List directly, avoiding the dozens or hundreds of GetComponent<> calls per frame in your UpdateSegments function.

100% agree with @Noblauch , Snake is a game that doesn’t make much sense with physics-based movement, unless you’re trying to do something really different from a traditional Snake.