# Preventing a Combination of Rigidbody Jump Force & Bounce Magnitude in Unity3D

I'm building a fairly simple marble racing game in Unity3D. The ball is a 3D physics object that moves only on the X and Y axes. It has the ability to roll left and right, and to jump. Pretty basic stuff, except that I've hit a game-breaking problem: When falling and striking the ground, the ball's bounce magnitude can be combined with its jumping force to create an extra-high jump. This means that, with well-timed button presses, the player can cause the ball to bounce exponentially higher, reaching unintended heights. I can't properly design levels until this glitch is fixed. I've illustrated this example:

The jumping, however, isn't as simple as just firing the ball straight upwards. In order to facilitate more complexity in the level design, I've programmed the jump angle to be relative to the surface that the ball is rolling on.

Figure 3, in that illustration, is how my game works thus far; not Figure 4. This makes solving for the bounce + jump problem much more challenging, because I can't simply measure and set an exact force or velocity on the Y-axis. Doing so results in weird behaviour, which becomes dramatically more noticeable as the ball travels on steeper slopes.

So far, I've been able to conceive a solution to all the other design problems in this game and then find out how to program them, but this one has me stuck. I've attempted a number of different approaches, but none of them have worked.

Here's the C# script that controls the ball's jumping:

using UnityEngine;
using System.Collections;

public class BallJumping : MonoBehaviour {

public System.Action onJump;
public Rigidbody objRigidbody; // Set this to the player
public bool isGrounded; // Determines whether or not the ball is on the ground
public Transform groundChecker; // A child object that's slightly larger than the ball
public LayerMask whatIsGround; // Determines what layers qualify as ground
public AudioClip jumpSFX;
public AudioClip stickyJumpSFX;
private float p_WillJumpTimeRemaining; // Grace periods before/after hitting the ground to trigger jump
private float p_CanJumpTimeRemaining;
public float earlyJumpToleranceDuration = 0.2f;
public float lateJumpToleranceDuration = 0.2f;
public float jump = 500f; // Jumping power
private float halfJump = 250f; // Used for the sticky puddles
public bool stuck = false; // Used for sticky materials
private float contactX;
private float contactY;

// Input for jumping
void Update () {
if (Input.GetButtonDown ("Jump") && isGrounded == true) {
ProcessJump();
}
}

// Continuously checks whether or not the ball is on the ground
void FixedUpdate () {
if (Physics.CheckSphere (groundChecker.position, groundRadius, whatIsGround) == true) {
isGrounded = true;
} else {
isGrounded = false;
}
}

// Sets a grace period for before or after the ball contacts the ground for jumping input
void ProcessJump () {
bool boolGetJump = Input.GetButtonDown("Jump");

if (boolGetJump && isGrounded == false) {
p_WillJumpTimeRemaining = earlyJumpToleranceDuration;
} else {
if (p_WillJumpTimeRemaining > 0) {
p_WillJumpTimeRemaining -= Time.fixedDeltaTime;
}
}

if (isGrounded) {
p_CanJumpTimeRemaining = lateJumpToleranceDuration;
}

if (isGrounded || p_WillJumpTimeRemaining > 0) {
Jump();
}

if (p_CanJumpTimeRemaining > 0) {
p_CanJumpTimeRemaining -= Time.fixedDeltaTime;
}
}

// Sticky puddles script -- hinders jumping while in the puddle
void OnTriggerEnter (Collider collision) {
if (collision.gameObject.tag == "Sticky") {
stuck = true;
}
}

void OnTriggerExit (Collider collision) {
if (collision.gameObject.tag == "Sticky") {
stuck = false;
}
}

// Calculates the normals for the jump angle
void OnCollisionStay (Collision collision) {
Debug.Log ("Collision.");
foreach (ContactPoint contact in collision.contacts) {
contactX = contact.normal.x;
contactY = contact.normal.y;
}
}

// Controls jumping
void Jump() {
Debug.Log ("Jump.");
p_WillJumpTimeRemaining = 0.0f;
p_CanJumpTimeRemaining = 0.0f;
halfJump = jump * 0.5f; // Cuts jumping force in half while in a sticky puddle

GetComponent<AudioSource>().volume = 1;
GetComponent<AudioSource>().pitch = Random.Range (0.9f, 1.1f);

if (stuck == false) {
objRigidbody.AddForce (contactX * jump, contactY * jump, 0);
GetComponent<AudioSource>().clip = jumpSFX;
GetComponent<AudioSource>().Play ();
}
else if (stuck == true) {
objRigidbody.AddForce (contactX * halfJump, contactY * halfJump, 0);
GetComponent<AudioSource>().clip = stickyJumpSFX;
GetComponent<AudioSource>().Play ();
}

if (onJump != null) {
onJump();
}
}
}


My latest attempt was to try jump - rigidbody.velocity.magnitude * 50 , to reduce the jumping power by the speed that the ball is travelling. It almost solved the bounce + jump problem, by proportionally reducing the jump force down to zero as the ball's speed reached what seemed to be the equivalent in velocity.magnitude. It worked from a standstill, but the problem is, it also accounts for the magnitude while the ball is grounded, preventing the ball from rolling at full speed and jumping. I was close, but not quite there!

I'm a novice programmer, and I'm stumped here. Can anyone help me find a creative solution to this problem? As long as the player is able to continuously bounce and jump higher and higher, I can't design any levels, because they'll all just be able to be cheated through. I'd love to move onward -- this issue has been holding me back for a long time, so I'd greatly appreciate some advice!

• Nice question:) did you try playing around with physic materials? You could set the bounciness of the ground to zero (or very low value). Maybe also the player, it depends. – M156 Apr 25 '16 at 7:29

First of all, i want to say that your question is very well written and it's a pleasure :), you would just need to remove what's not necessary in the code (audiosources, etc.) and it would be perfect. Cheers for that.

For the answer, you could clamp your velocity when jumping, which would prevent you from reaching too high speeds when pressing the jump button.

While I personally love bunny hopping... As a starting point we should know the intended "Jump Speed" as a delta velocity. This figure represents the velocity increase (in the line with the "Jump Normal") during the instant of jumping once.

Any velocity the player already has in line with the Jump Normal can be seen as a pre-existing "Jump Energy". This leads to a straightforward solution: The instantaneous delta velocity can be limited such that it never results in the player being accelerated beyond the target velocity.

In order to measure your pre-existing Jump Speed, we can take the dot product of your normalized your Jump Vector, and your player's velocity:

Vector2 JumpNormal = Vector2(contactX, contactY).normalized;
Vector2 PlayerVelocity = objRigidbody.velocity;
float ExistingSpeed = Vector2.Dot(PlayerVelocity, JumpNormal);
if (ExistingSpeed < 0) ExistingSpeed = 0;


The "Existing Speed" is also forced non-negative here; When the player is falling, a negative existing jump speed will compensate for their falling, allowing them to bounce on thin air if they trigger the jump while falling.

Now that we know how much to reduce the delta velocity precisely, we can compute the effective "Jump Vector" by scaling the Jump Normal to the target delta velocity.

float AdjustedSpeed = JumpSpeed - ExistingSpeed;