- Home /
Better 2D Sprite Jump in 3D Plane
Hey there, I'm having trouble with creating a 'jumping' script for my game. I have scoured the internet for almost a week looking for a solution to this, but everyone seems to solve the problem in many different ways that don't quite fit or help my issue, so finally, I am posting in hopes this can help me and perhaps others who may have the issue.
My player is a 2D car sprite that drives along a road collecting gas and such, but I need to have a good jump mechanic to get over obstacles. At the moment the player sort of teleports up and floats down which is quite jarring. My player also has a time-limit health bar above their head, just so you know I guess. See the attached image for more background information. ![alt text][1] [1]: /storage/temp/117024-example.png Now, here's my MoveForward Script (I need to the change the awful name I know)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveForward : MonoBehaviour
{
//Variables for the pacing of general movement
//thrust handles general movements such as left and right
public float thrust;
public float jumpForce = 2.0f;
//declares the variable 'rb' as a 2D Rigidbody
public Rigidbody2D rb;
public Vector2 jump;
//Bool checks whether player is on the ground or not, if so, they can jump, if not, they can't
public bool isGrounded;
void Start()
{
//Gets the Rigidbody component
rb = gameObject.GetComponent<Rigidbody2D>();
}
void OnCollisionStay2D(Collision2D collision)
{
//On collision with the ground this becomes true, once the collision stops it becomes false!
isGrounded = true;
}
void FixedUpdate()
{
//General Movements, Left, Right, and Down
//Drive Left
if (Input.GetKey(KeyCode.A))
{
rb.AddForce(transform.position += Vector3.left * thrust * Time.deltaTime);
}
//Drive Right
if (Input.GetKey(KeyCode.D))
{
rb.AddForce(transform.position += Vector3.right * thrust * Time.deltaTime);
}
//Jump Script
if (Input.GetKey(KeyCode.Space) && isGrounded)
{
rb.AddForce(transform.position += Vector3.up * jumpForce * Time.deltaTime);
isGrounded = false;
}
}
}
Answer by Eno-Khaon · May 17, 2018 at 05:09 AM
Your movement scheme at the moment is... thoroughly confusing right now, to say the least.
Here's a rundown of how your car moves at the moment:
Let's say your car starts at (-41, 5, 0). You hold 'D' to move right. Well, here's how you move instead:
First, we take your position (-41, 5, 0) and apply that as a force, scaled by Time.deltaTime squared, since AddForce() inherently provides that modification. So, in one second of input, you can anticipate moving approximately 40/50 units *left* (since you slightly tone down that movement using Vector2.right * thrust
), while also trying to move up slightly. Default gravity would push you down by 9.81 units per second, so you wouldn't successfully rise into the air in a meaningful way.
On top of all of this, you use transform.position += Vector2.right
inside AddForce(), which simply makes an even bigger mess of things.
First off, you don't need to factor your position into your use of AddForce(). You also don't need to factor in Time.deltaTime.
// Push object toward world-right
rb.AddForce(Vector2.right * thrust);
// ... is the same as...
rb.AddForce(Vector2.right * thrust, ForceMode2D.Force);
// If you want to ignore your car's mass...
rb.AddForce(Vector2.right * thrust * rb.mass, ForceMode2D.Force);
Do note that you'll probably want to reduce your current thrust value by 50 times (based on Unity's default setting for Time.fixedDeltaTime of 0.02, or 50 per second).
As for jumping itself, there's a different matter to deal with. You only need to apply a jumping force while you're currently on the ground. Therefore, you only need to apply that force once at a time.
I've previously provided an example function here on how to calculate the force necessary to jump to a specified height, so after that's calculated, you simply apply it:
// Apply the force immediately, rather than over 1 second
// Also, this ignores mass by factoring it in
rb.AddForce(Vector2.up * jumpForce * rb.mass, ForceMode2D.Impulse);
A confounding factor in all of this, however, is the fact that Input occurs during the Update() function. If you try to receive Input during FixedUpdate(), it can miss key moments, such as GetKeyDown.
Therefore, I recommend the following:
Get your left/right controls and your jumping during Update(), then apply the left/right during FixedUpdate().
Vector2 movementInput = Vector2.zero;
void Update()
{
movementInput = new Vector2(Input.GetAxis("Horizontal"), 0.0f);
// Ideally, use GetButtonDown() instead, but may need to be defined in the input manager
// Using Spacebar instead as a fallback
if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
// A single, immediate force can more-safely be applied outside of FixedUpdate()
// But don't count on it for a constant force
rb.AddForce(Vector2.up * jumpForce * rb.mass, ForceMode2D.Impulse);
}
}
void FixedUpdate()
{
rb.AddForce(movementInput * thrust * rb.mass);
}
Edit: Whoops, made some changes for Physics2D-based algorithms.
Thanks for this, it was really helpful and the explanations were really useful too! I'm not a good programmer but it feels like this really helped put things in perspective. I did end up changing to 'GetButtonDown' but it all works pretty well :) Thanks!
Your answer
Follow this Question
Related Questions
I have a problem with my sidescroller movement script 0 Answers
Jumping effectively in a 2D sidescroller 0 Answers
Double Jump 1 Answer
a better movement code C# 3 Answers