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 float groundRadius = 0.6f;
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!