- Home /
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));
}
else
{
rb.velocity = new Vector2(moveInput * speed, rb.velocity.y);
}
The Questions:
How can I move the characters using AddForce but make their movement as snappy, and constant, and as similar as possible to using .velocity ?
Is my current solution really terrible for performance, or will it cause other issues I'm unaware of?
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!!
Answer by rh_galaxy · May 05, 2020 at 03:14 PM
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.
Thanks for the reply!! I had a play with using AddForce and tweaking mass, gravity, linear drag etc, but I still wasn't too happy with the character movement.. I ended up doing a build to test whether my current method of switching between rb.AddForce and using rb.velocity to move the characters affected performance and it seemed to work ok. I think it was only lagging when I played it in the editor, but seemed ok in the build..
Answer by tadadosi · May 10, 2020 at 05:39 PM
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;
else
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;
else
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);
else
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);
else
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);
}
else
{
p1_rb.AddForce(direction * force, mode);
}
}
}
This is the setup in the scene:
I just saw a mistake that should be fixed by erasing this IF in the FixedUpdate():
if (distance >= maxDistance)
We don't need to actually check for the distance, all is handled by the part that remains:
// 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_$$anonymous$$oveX * speed, p0_rb.velocity.y);
else
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_$$anonymous$$oveX * speed, p1_rb.velocity.y);
else
p1_Velocity = new Vector2(p1_rb.velocity.x, p1_rb.velocity.y);
Hey @tadadosi thanks so much for taking the time to write that all up!! I think your solution is similar to what I have the moment: if the players are grounded then use .velocity. But If one player is well below the other player and not grounded then it means that he's hanging, so in that case, use AddForce. Is that correct?
Hey! @cookiecrayon no problem! Yep, let the engine handle the velocity and just add force when needed.