Menu
CoddyTech

Never Flat

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

A bounce off a brick's corner can leave the ball nearly level, crossing the field again and again while the player waits. The fix belongs in the speed fix: if the direction's y part is too small, set it to a minimum and normalize again.

Mathf.Abs gives a number's size without its sign, so one test catches balls going up and down. Mathf.Sign gives -1 for a negative number and 1 otherwise, so a ball going down still goes down.

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

With a minimum of 0.4, the direction (0.99, -0.12, 0) gets y -0.4 and, normalized, about (0.93, -0.37, 0): still right and down, now steep enough to come back. Steeper directions are left alone.

challenge icon

Challenge

Medium

Keep the ball from crossing the field almost flat.

  1. In Ball, add public float minVertical = 0.3f;
  2. In FixedUpdate, take Vector3 direction = rb.linearVelocity.normalized;. If Mathf.Abs(direction.y) < minVertical, set direction.y = Mathf.Sign(direction.y) * minVertical; and normalize again.
  3. Set rb.linearVelocity = direction * speed;

Press Play: the ball never crosses the field flat for long. 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);
    }
}

All lessons in Unity Game: Brick Breaker

Practice on your own: Unity playground