move 2d character affected by physics with velocity and/or add force

I’ve been stuck on this for almost a week now and haven’t quite found a solution that works for me, so thought I’d finally ask a question here…

The Context:

I have two Dynamic Rigidbody2D characters attached to each other by a Distance Joint 2D.
Their left and right movement is controlled by using:

Public float speed;

moveInput = Input.GetAxisRaw(“Arrows”);

rb.velocity = new Vector2(moveInput * speed, rb.velocity.y));

The Issues

This works really well, BUT using ‘rb.velocity’ to control the characters’ movements cancels out any physics, which are necessary to get the players ‘swinging’ when they aren’t grounded and being dragged by the other player on a higher platform…

I’ve tried replacing the code with the following, which gets the swinging effect, but the characters pick up too much speed when they walk and especially when they jump/fall, and their movement is too slow when they first start walking

public float speed;

moveInput = Input.GetAxisRaw(“Arrows”);

rb.AddForce(new Vector2(moveInput * speed, rb.velocity.y));

I’ve tried using a maxSpeed to stop them going too fast, as well as ForceMode2D.Impulse to make the initial speed faster, but nothing I did was as good for controlling the character as using .velocity.

The Current Solution:

Right now I have something that kind of works, but I’m worried it will affect performance because it lags sometimes when I playtest…

(In Fixed Update)

Private float distance;

Public float distanceThreshold;

Public float swingSpeed;

distance = transform.position.y - otherPlayer.position.y;

if (distance < distanceThreshold && isGrounded == false)


_rb.AddForce(new Vector2(moveInput * swingingSpeed, rb.velocity.y));




_rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);


The Questions:

  1. How can I move the characters using AddForce but make their movement as snappy, and constant, and as similar as possible to using .velocity ?

  2. Is my current solution really terrible for performance, or will it cause other issues I’m unaware of?

  3. Is there perhaps another/better/simpler way to achieve what I’m trying to do?

This is the game I’m expanding on if it helps with the context… You can see how the players don’t exactly ‘swing’ how they should and when they should…

Thanks in advance!!

What if you do AddForce every physics timestep (in FixedUpdate) when input is active, and modify rb Mass and Linear Drag and the force in AddForce to get a good acceleration and top velocity. Use AddForce(jumpForce, ForceMode2D.Impulse) once when jumping. Regarding performance I don’t think it matters which way you chose.

As you say you can’t both set velocity and add force.

Hello @cookiecrayon! I came up with a code that seems to work like your intended behaviour, just add it to a new GameObject, add 2 characters with a size (1,1) and it should work, also added some comments to try to explain a little what I did. Hopefully it will be of help to your project.

Edit0: I realized that I had an error in FixedUpdate, I fixed it and now it should work just fine.

using UnityEngine;

public class Cookie_PlayersController : MonoBehaviour
    public float speed = 3f;
    public float hangingForce = 100f;
    public float jumpImpulse = 12f;
    public float maxDistance = 3.9f;
    public Rigidbody2D p0_rb;
    public Rigidbody2D p1_rb;
    public Transform p0_GroundedCheck; // Simple gameobject below on the players feet
    public Transform p1_GroundedCheck; // Simple gameobject below on the players feet
    public LayerMask WhatIsGround; // Layer to determinate what should Physics2D.OverlapBox see as ground

    private Vector2 p0_Velocity;
    private Vector2 p1_Velocity;

    [SerializeField] private float distance;
    [SerializeField] private float p0_MoveX;
    [SerializeField] private float p1_MoveX;

    [SerializeField] private bool p0_isGrounded;
    [SerializeField] private bool p1_isGrounded;

    private void Update()
        p0_isGrounded = Physics2D.OverlapBox(p0_GroundedCheck.position, new Vector2(0.60f, 0.07f), 0, WhatIsGround);
        p1_isGrounded = Physics2D.OverlapBox(p1_GroundedCheck.position, new Vector2(0.60f, 0.07f), 0, WhatIsGround);

        distance = Vector2.Distance(p0_rb.position, p1_rb.position);

        // Player 0
        if (Input.GetKey(KeyCode.D))
            p0_MoveX = 1;
        else if (Input.GetKey(KeyCode.A))
            p0_MoveX = -1;
            p0_MoveX = 0;

        // WHILE HANGING
        if (Input.GetKeyDown(KeyCode.D) && !p0_isGrounded)
            AddForceToPlayer(0, Vector2.right, hangingForce, ForceMode2D.Force);
        if (Input.GetKeyDown(KeyCode.A) && !p0_isGrounded)
            AddForceToPlayer(0, Vector2.left, hangingForce, ForceMode2D.Force);

        // Player 1
        if (Input.GetKey(KeyCode.RightArrow))
            p1_MoveX = 1;
        else if (Input.GetKey(KeyCode.LeftArrow))
            p1_MoveX = -1;
            p1_MoveX = 0;

        // WHILE HANGING
        // You can use the same keys as inputs to add force while hanging, you just need to check if 
        // they are not grounded and if they actually are hanging distance >= maxDistance
        if (Input.GetKeyDown(KeyCode.RightArrow) && !p1_isGrounded && distance >= maxDistance)
            AddForceToPlayer(1, Vector2.right, hangingForce, ForceMode2D.Force);
        if (Input.GetKeyDown(KeyCode.LeftArrow) && !p1_isGrounded && distance >= maxDistance)
            AddForceToPlayer(1, Vector2.left, hangingForce, ForceMode2D.Force);

        // JUMP
        if (Input.GetKeyDown(KeyCode.W) && p0_isGrounded)
            AddForceToPlayer(0, Vector2.up, jumpImpulse, ForceMode2D.Impulse);
        if (Input.GetKeyDown(KeyCode.UpArrow) && p1_isGrounded)
            AddForceToPlayer(1, Vector2.up, jumpImpulse, ForceMode2D.Impulse);

    private void FixedUpdate()
        // Check if they are ground or they are on top of the other, that means that they should
        // be affected by the input and the speed.
        // Else they should be just handled by the physics and you shouldn't try to mess with
        // the velocity, just leave it be and AddForce.

        if (p0_isGrounded || p0_rb.position.y > p1_rb.position.y)
            p0_Velocity = new Vector2(p0_MoveX * speed, p0_rb.velocity.y);
            p0_Velocity = new Vector2(p0_rb.velocity.x, p0_rb.velocity.y);

        if (p1_isGrounded || p1_rb.position.y > p0_rb.position.y)
            p1_Velocity = new Vector2(p1_MoveX * speed, p1_rb.velocity.y);
            p1_Velocity = new Vector2(p1_rb.velocity.x, p1_rb.velocity.y);  

        // Just pass the new velocity based on the logic
        p0_rb.velocity = p0_Velocity;
        p1_rb.velocity = p1_Velocity;

    private void AddForceToPlayer(int ID, Vector2 direction, float force, ForceMode2D mode)
        if (ID == 0)
            p0_rb.AddForce(direction * force, mode);
            p1_rb.AddForce(direction * force, mode);

This is the setup in the scene: