0
\$\begingroup\$

I'm making a 2D platformer with Unity. I used transform.position for movement but it caused some problems like vibration, so I decided to use AddForce.ForceMode2D.Force instead. Since this uses mass for moving the player, it makes the player either so heavy and hard to control (even when the mass is 1) or so quick and fast. I tried changing the mass and speed but I haven't found the setting that makes a smooth movement. Also, the jumping has a problem. The player will be suspended in the air for a few seconds after the jumping. I'm just looking for a way to make AddForcesmoother and solve these problems.

Here's my code:

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

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float jumpSpeed;

    public float maxSpeed = 5f;
    public float maxAcceleration = 50f;
    public float maxDeceleration = 100f;

    [SerializeField] private LayerMask ground;
    private PlayerActionControls playerActionControls;
    private Rigidbody2D rb;
    private PolygonCollider2D pol;
    private Animator animator;
    private Vector2 movementInput;

    private void Awake()
    {
        playerActionControls = new PlayerActionControls();
        rb = GetComponent<Rigidbody2D>();
        pol = GetComponent<PolygonCollider2D>();
        animator = GetComponent<Animator>();
    }

    private void OnEnable()
    {

        playerActionControls.Enable();

    }

    private void OnDisable()
    {

        playerActionControls.Disable();
    }

    void Start()
    {
        playerActionControls.Land.Jump.performed += ctx => Jump(ctx.ReadValue<float>());

    }

    private void Jump(float val)
    {
        if (val == 1 && IsGrounded())
        {
            rb.AddForce(new Vector2(0, jumpSpeed), ForceMode2D.Impulse);

        }
    }
    private bool IsGrounded()
    {

        Vector2 topLeftPoint = transform.position;
        topLeftPoint.x -= pol.bounds.extents.x;
        topLeftPoint.y += pol.bounds.extents.y;

        Vector2 bottomRightPoint = transform.position;
        bottomRightPoint.x += pol.bounds.extents.x;
        bottomRightPoint.y -= pol.bounds.extents.y;

        return Physics2D.OverlapArea(topLeftPoint, bottomRightPoint, ground);
    }
    void FixedUpdate()
    {
       
     Vector2 movementInput = playerActionControls.Land.Move.ReadValue<Vector2>();

     movementInput = Vector2.ClampMagnitude(movementInput, 1f);
     Vector2 desiredVelocity = movementInput * maxSpeed;
     AccelerateTo(desiredVelocity);

    
    var scale = transform.localScale;
    if(scale.x * rb.velocity.x < 0) {
        scale.x *= -1;
        transform.localScale = scale;
    }
    
    void AccelerateTo(Vector2 DesiredVelocity) {
    
    var deltaV = desiredVelocity - rb.velocity;
    var acceleration = deltaV / Time.deltaTime;

    
    float limit = maxAcceleration;
    if (Vector3.Dot(rb.velocity, desiredVelocity) <= 0f) 
        limit = maxDeceleration;       
    acceleration = Vector2.ClampMagnitude(acceleration, limit);

    Vector2 force = rb.mass * acceleration;
    rb.AddForce(force);
   }
 }
}
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

It doesn't sound like these problems are caused by ForceMode2D.Force at all. It sounds like they're caused by how you're choosing to use it.

You're treating the player character like they're a spaceship, firing a thruster with a certain force vector and accelerating endlessly in that direction. That's not really how a biped moves. But treat a biped like a spaceship, and they'll handle like a spaceship (ie. floaty - not feeling like a tight, snappy, platformer character)

Unlike a spaceship, a platformer character usually has a fixed max speed. They'll accelerate up to that max speed quite quickly, and decelerate from their max speed to a stop even faster when the player lets go of the stick or tries to turn around.

Here's a way you can get this kind of behaviour:

public float maxSpeed = 5f;

// Tune this quite high, until the character feels nicely responsive.
public float maxAcceleration = 50f;

// Tune this even higher, so the character stops quickly when you let go.
public float maxDeceleration = 100f;

// Handle continuous motions in FixedUpdate, not Update.
void FixedUpdate()
{
    Vector2 movementInput = playerActionControls.Land.Move.ReadValue<Vector2>();

    // Ensure our movement input never exceeds a radius of 1.
    movementInput = Vector2.ClampMagnitude(movementInput, 1f);

    // Accelerate up to, but not past, our max speed.
    Vector2 desiredVelocity = movementInput * maxSpeed;
    AccelerateTo(desiredVelocity);

    // Handle flipping. (Doesn't need to be a separate function call)
    var scale = transform.localScale;
    if(scale.x * rb.velocity.x < 0) {
        scale.x *= -1;
        transform.localScale = scale;
    }
}

void AccelerateTo(Vector2 desiredVelocity) {
    // Compute the acceleration we'd need to reach our desired velocity in this time step.
    var deltaV = desiredVelocity - rb.velocity;
    var acceleration = deltaV / Time.deltaTime;

    // Enforce our maximum acceleration limits when speeding up or slowing down.
    float limit = maxAcceleration;
    if (Vector3.Dot(rb.velocity, desiredVelocity) <= 0f) 
        limit = maxDeceleration;       
    acceleration = Vector2.ClampMagnitude(acceleration, limit);

    // Compute and apply a force that gets us this acceleration.
    Vector2 force = rb.mass * acceleration;
    rb.AddForce(force);
}

Note that your current code applies the player's movement exactly the same on the ground as in the air, and allows analog stick input to push the character vertically. I'm assuming that's what you want, so I've preserved that in the code I've shown here.

However, a more typical platformer behaviour would be to check if you're on the ground, and if so, apply your analog stick acceleration only perpendicular to the ground normal. Or if not, apply your acceleration only along the x axis, with reduced acceleration limits (depending on how much air steering you want to allow).

\$\endgroup\$
2
  • \$\begingroup\$ Thank you very much for your answer. The movement is now smooth and also flipping is working properly. I know that this is not directly related to my question but I want to know why the player vibrates again when collides with something. Besides, it suspends for a few seconds after jumping and can move in the air in this situation. I'm just asking this because I think it could be related to what you said about the ground and the air. (Also I updated the code.) \$\endgroup\$
    – Ali
    Dec 12, 2020 at 3:28
  • \$\begingroup\$ The vibration is like the player sprite spins (like the flipping but much more faster). Actually, I decided to use Add.Force because I thought I would not have these kind of problems. I'm not sure about the suspension after jumping. \$\endgroup\$
    – Ali
    Dec 12, 2020 at 17:45

You must log in to answer this question.

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