Good evening, folks!
I’m designing a 2D platformer in Unity (version 5.3.1f1), but I’m having a lot of trouble preserving my character’s momentum, specifically while airborne. I’ve done some research and tried to implement it in a few different ways based on older search results, but nothing so far has worked.
Essentially, my problem is this: the player can move left and right just fine, and they can even jump around a bit, but whenever they do, they always jump straight up. Any speed they’d built going in either direction is immediately cancelled, and emulating the advice in the thread linked above isn’t helping. It’s still possible to jump sideways by inputting left or right after the jump, but this does not make for fun or precise movement.
I think I may have narrowed down where the issue is coming from via testing, but I’ve no idea how to actually solve it. I was hoping somebody here could help me out with it?
I’ll include my player controller script below, along with my hypothesis on where the problem is and what I’ve tried to do to fix it. I apologize if it ends up kind of longish or I break the forum formatting; it’s actually a lot shorter than it looks. I just tend to over-comment (I’m actually an English student, not a programmer, so I need the baby steps). Sorry if that makes it a pain to read!
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour {
// Determines player facing, which simplifies animations.
// Returns true if facing right, else false.
public bool facingRight = true;
// Returns true if player is able to jump, ie has not done so yet.
public bool jump = false;
// The first checks if the player is trying to double jump, the second keeps track
// of whether they still have a jump to expend.
public bool doubleJump = false;
public bool hasdoubleJumped = false;
// Amount of force exerted on the player when they move.
// Increase to cause the player to accelerate from idle position faster.
public float moveForce = 350f;
// Maximum speed, prevents infinite acceleration.
public float maxSpeed = .5f;
// Amount of force applied to player when they jump.
public float jumpForce = 700;
public float doubleJumpForce = 500f;
// I don't know what this is going to do yet, honestly.
public Transform groundCheck;
// Returns true if player is on a valid, flat surface from which they can jump normally.
private bool grounded = false;
// This is a dirty hack, you might want to come back to this.
private bool onPlatform = false;
// This stuff lets the player animate properly and interact with physics objects.
private Animator anim;
private Rigidbody2D rb2d;
// Goldfish collected
private int count;
public Text countText;
// These are all of the sound effects the player character can make.
public AudioClip GoldfishPickup;
public AudioClip NormalHop;
public AudioClip DoubleJump;
void Awake ()
{
// Just fetching and storing component references.
// Nothing to see here, move along.
anim = GetComponent <Animator>();
rb2d = GetComponent<Rigidbody2D>();
count = 0;
SetCountText();
}
void Update()
{
// Linecast draws a line down and returns a boolean (true) if it collides with
// groundCheck, at the player's feet. The latter part of the code ensures it is
// only checking the ground layer, and not other stuff that you might have going on.
grounded = Physics2D.Linecast(transform.position, groundCheck.position,
1 << LayerMask.NameToLayer("Ground"));
// This is doing the same thing with platforms.
onPlatform = Physics2D.Linecast(transform.position, groundCheck.position,
1 << LayerMask.NameToLayer("Platform"));
// Checks to see if the jump button is pressed, and also if the player is grounded.
// If you want to add a double jump later, this is where you need to play with the code!
if (Input.GetButtonDown("Jump") && (grounded || onPlatform))
jump = true;
// Allows you to double jump while airborn.
else if (Input.GetButtonDown("Jump") && (!grounded || !onPlatform))
{
doubleJump = true;
}
// Just animation stuff for jumping.
else if (Input.GetButtonUp("Jump"))
{
anim.ResetTrigger("Jump");
}
}
// Physics stuff always goes here.
void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
// Sets animation speed. Should even get faster as the player accelerates!
// The Mathf.Abs bit is using the absolute value of the above variable, since
// we're using Flip() to cheat and reorient our sprites by multiplying the
// X axis by -1. We always want the animation speed to be positive, right?
anim.SetFloat("Speed", Mathf.Abs(horizontal));
// If the amount of force being applied to the player is less than the
// maximum allowed, we're going to add some more. This works in both directions,
// since horizontal can be a negative number.
if (horizontal * rb2d.velocity.x < maxSpeed)
rb2d.AddForce(Vector2.right * horizontal * moveForce);
// If we are going too fast somehow, this will slow us down again.
// Mathf.Sign is doing something fun; it returns a 1 or -1, corresponding to whether
// the velocity is currently positive or negative (left or right). This way we can
// clamp the max speed in either direction with one line of code.
// We're leaving the y axis alone because we don't want to change the player's jump speed.
if (Mathf.Abs(rb2d.velocity.x) > maxSpeed && !jump)
rb2d.velocity = new Vector2(Mathf.Sign(rb2d.velocity.x) * maxSpeed, rb2d.velocity.y);
// This just makes sure we're animating in the same direction that we're moving.
// It does this by comparing the positive or negative value for horizontal, with the
// boolean for which direction we're currently facing. Positive values should mean we're
// facing to the right, negative to the left.
// (I actually had to invert these > < signs and I don't know why it works this way.)
if (horizontal < 0 && !facingRight)
Flip();
else if (horizontal > 0 && facingRight)
Flip();
// This is making sure you get your double jump back when you land.
if (grounded)
hasdoubleJumped = false;
if(jump)
{
// Tells the animator we're jumping now.
AudioSource.PlayClipAtPoint(NormalHop, transform.position);
anim.SetTrigger("Jump");
rb2d.AddForce(new Vector2(rb2d.velocity.x, jumpForce));
jump = false; // This also needs to be changed in the event of a double jump!
}
// In theory, this triggers if the player CAN double jump (has single jumped), and has not
// done so already.
else if (doubleJump && !hasdoubleJumped)
{
AudioSource.PlayClipAtPoint(DoubleJump, transform.position);
// This is cancelling out the stacking double jump height. Might want to remove it.
rb2d.AddForce(new Vector2(rb2d.velocity.x, -1*jumpForce));
rb2d.AddForce(new Vector2(rb2d.velocity.x, doubleJumpForce));
doubleJump = false; // This also needs to be changed in the event of a double jump!
hasdoubleJumped = true;
}
}
// This method flips the orientation of your sprite, by scaling the X axis of the sprite by -1.
// This reduces the number of animations you need to draw manually. You're welcome, future me.
void Flip()
{
Vector3 spriteScale = transform.localScale;
spriteScale.x *= -1;
transform.localScale = spriteScale;
facingRight = !facingRight;
}
//OnTriggerEnter2D is called whenever this object overlaps with a trigger collider.
void OnTriggerEnter2D(Collider2D other)
{
//Check the provided Collider2D parameter other to see if it is tagged "PickUp", if it is...
if (other.gameObject.CompareTag("Pickup"))
{
// Plays a sound, deactivates the game object, and then increments total collected.
AudioSource.PlayClipAtPoint(GoldfishPickup, transform.position);
other.gameObject.SetActive(false);
count = count + 1;
SetCountText();
}
}
// Method refreshes total of pickups collected in the UI when called.
void SetCountText()
{
countText.text = "x" + count.ToString();
}
}
Again, sorry if that was way too much to sift through, but I didn’t want to risk not including some method or another that might actually turn out to be the problem.
I’m fairly certain that the problem is in the bit of code that defines how a player jumps, in line 147. My gut says it’s specifically this bit:
…which, in theory, should be passing both the old value of the player’s velocity on the x axis, with the new force of the jump on the y axis, as arguments. Except, clearly, this isn’t happening. I determined this must be the problem because if I replace rb2d.velocity.x with any static value, positive or negative, the character will hop in that direction! For instance, a value of 30 will cause them to hop to the right; -30 will steer them to the left. This is due to the flip function.
So that’s cool, it tells the line of code is definitely working as intended, in that it is allowing the player to jump while moving on the x axis, but that rb2d.velocity.x is not what I think it is.
A friend of mine took a whack at it, too, and we wrote it a few different ways, but I’ve got no clue what the solution is. Am I just using it incorrectly? Any help would be much appreciated. After all, I can’t imagine a platformer being fun to play if jumping doesn’t even feel good!
Sorry this is such a basic question, and thanks in advance!