Menu
CoddyTech

Never Flat

Lesson 10 of 16 in Coddy's Unity Game: Brick Breaker course.

A ball can end up flying almost sideways: a bounce off a brick's corner or a push from another ball can leave it nearly level, crossing the field again and again without coming down. The game stalls while the player waits for it to come down. The fix belongs in the speed fix: after making the direction 1 unit long, if its y part is too small, set y to a minimum and normalize again.

Two Mathf methods do the work. Mathf.Abs gives a number's size without its sign, so one test catches balls going up and balls going down. Mathf.Sign gives -1 for a negative number and 1 otherwise, so the new y keeps the ball's way: a ball going down still goes down.

float drift = -0.12f;
Debug.Log(Mathf.Abs(drift));    // 0.12
Debug.Log(Mathf.Sign(drift));   // -1

Work it through with a minimum of 0.3 and the direction (0.99, -0.12, 0). The size of y, 0.12, is under 0.3, so y becomes -0.3, and (0.99, -0.3, 0) normalized is about (0.96, -0.29, 0): still going right and down, now steeply enough to come back. Normalizing brings y a little under the minimum again, and the next step tops it up, so it stays a little under 0.3. Steeper directions are left alone.

challenge icon

Challenge

Medium
  1. Add public float minVertical = 0.3f to Ball.
  2. In FixedUpdate(), take the direction: the velocity, normalized. If the size of its y is under minVertical, set y to minVertical with y's sign, and normalize the direction again.
  3. Then set the velocity to the direction times speed, as before.

Press Play, then Check.

Try it yourself

using UnityEngine;
using UnityEngine.InputSystem;

public class Ball : MonoBehaviour
{
    public Transform paddle;
    public float speed = 9f;

    Rigidbody rb;
    bool launched;

    void Awake()
    {
        rb = GetComponent<Rigidbody>();
        PhysicsMaterial bouncy = new PhysicsMaterial("Bouncy");
        bouncy.bounciness = 1f;
        bouncy.dynamicFriction = 0f;
        bouncy.staticFriction = 0f;
        bouncy.bounceCombine = PhysicsMaterialCombine.Maximum;
        bouncy.frictionCombine = PhysicsMaterialCombine.Minimum;
        GetComponent<Collider>().material = bouncy;
    }

    void Update()
    {
        if (launched) return;
        transform.position = paddle.position + Vector3.up * 0.5f;
        if (Keyboard.current.spaceKey.wasPressedThisFrame) Launch(new Vector3(0.3f, 1f, 0f));
    }

    public void Launch(Vector3 direction)
    {
        launched = true;
        rb.linearVelocity = direction.normalized * speed;
    }

    public void ResetToPaddle()
    {
        launched = false;
        rb.linearVelocity = Vector3.zero;
    }

    void FixedUpdate()
    {
        if (!launched) return;
        rb.linearVelocity = rb.linearVelocity.normalized * speed;
    }

    void OnCollisionEnter(Collision collision)
    {
        if (!collision.gameObject.CompareTag("Paddle")) return;
        float offset = transform.position.x - collision.transform.position.x;
        float halfWidth = collision.collider.bounds.size.x / 2f;
        float x = Mathf.Clamp(offset / halfWidth, -1f, 1f);
        rb.linearVelocity = new Vector3(x, 1f, 0f).normalized * speed;
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("DeathZone")) GameManager.Instance.BallLost(this);
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Unity Game: Brick Breaker