Busting out the break out clone

Estimated reading time 1 hours, 35 minutes

Welcome! Just like last time, we're doing a 20 games challenge development tutorial blog post dev log thing! As usual, my goal is to try to make something that's of interest to intermediate programmers who want better and more in-depth projects than doing random to-do apps or similar. 1

Of course, I want this to be interesting to me too, and so we're doing this 20 games challenge because I find games to be both be a great source of inspiration for going beyond the ho-hum boring webwork of connecting APIs via pipes that is many programmer's day jobs, and it's just fun! 2

These posts tend to be a bit long, so you can use the table of contents below to jump back to where you left off if you're returning and refreshed the page or something. And take the reading time estimate above with a grain of salt. Word count calculations like what I use for that are a little skewed when it comes to code snippet heavy blog posts, so it might take you a lot less than the noted time. Or, if you're really staring at the code or following along yourself, it might take you longer! As long as you get something out of this, then I'll be happy. Feel free to swing by the stream or shoot me an email or pull request if you've got comments or notes!

If you'd like to play the game, see the releases here

Initial setup

Just like the last two games, we'll be using LibGDX as our framework library to get access to basics like drawing textures, playing music, and having a game loop targetting 60FPS or similar. It's a nice framework, used to make games like Slay the Spire! While I don't care that much about card deck type games, I like the library itself. It's easy to use, and to get started I can use gdx-liftoff to generate the basic gradle project and pop it open in intelliji.

That's right. It's Java. This won't suprise anyone who follows my blog, but I do enjoy the JVM languages! I know that a lot of folks like the C family of languages for game programming, and rust has been on the rise in this area a bit too I think. But since I want to spend my time enjoying the puzzle of piecing together various patterns and fun things, I'm sticking with Java rather than trying to drop down a level and getting stuck on trying to draw a simple window. Speaking of windows, as you can see in the screenshot above, we've got a black screen running from the initial project. Let's tweak the two files we've got to be slightly more useful.

package spare.peetseater.games;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GameRunner extends Game {

    public SpriteBatch batch;
    AssetManager assetManager;

    @Override
    public void create() {
        assetManager = new AssetManager();
        batch = new SpriteBatch();
        setScreen(new InitialLoadingScreen(this)); // <-- this wont compile yet.
    }

    @Override
    public void dispose() {
        super.dispose();
        assetManager.dispose();
    }
}

Our first tweak is to declare an asset manager, which we'll use to load textures and other game assets. Then a SpriteBatch to handle drawing said assets everywhere. If you're unfamiliar with how these things work, then it should become slightly more clear in a moment as we tweak our first screen into a loading screen:

package spare.peetseater.games;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.ScreenUtils;

public class InitialLoadingScreen implements Screen {
    private final AssetManager assetManager;
    private final SpriteBatch batch;
    private final BitmapFont bitmapFont;
    private float elapsedSeconds;
    private int pulse;

    public InitialLoadingScreen(GameRunner game) {
        this.bitmapFont = new BitmapFont(false);
        this.assetManager = game.assetManager;
        this.batch = game.batch;
        this.elapsedSeconds = 0;
        this.pulse = 1;
    }

    @Override
    public void render(float delta) {
        assetManager.update(17);
        float alpha = MathUtils.lerp(0, 100, elapsedSeconds);
        int loaded = (int)(assetManager.getProgress() * 100);
        Color color = new Color(0,1,0,alpha/100f);
        ScreenUtils.clear(Color.BLACK);
        batch.begin();
        batch.enableBlending();
        bitmapFont.setColor(color);
        bitmapFont.draw(
            batch, 
            String.format("Loading %d%%", loaded), 
            0, Gdx.graphics.getHeight()/2f, // x,y placement
            Gdx.graphics.getWidth(), // desired width for placement
            Align.center,
            false  // text wrap
        );
        batch.end();
        elapsedSeconds += delta * pulse;
        if (elapsedSeconds > 1) {
            pulse = -1;
        } else if (elapsedSeconds < 0) {
            pulse = 1;
        }
    }

    @Override
    public void dispose() {
        this.bitmapFont.dispose();
    }
}

That's a decent chunk of code to parse, but it's not terribly hard to explain I think. First off, imports and constructors are self explanatory. Of note in the constructor though is that we construct a BitmapFont resource directly. We do this because for our first basic loading screen we don't have any fonts or other assets loaded, so using the built in basic bitmap font will let us draw text to the screen in a default font. We'll want to use a freetype font later, but for now, this works as a stopgap while the game sets up.

elaspedSeconds and pulse are initialized, and pulse's purpose becomes more clear when we look at the render function. Pulse swaps from -1 to 1 depending on if the elasped time has reached a second or not. This has a pleasant effect when we combine it with linear interpolation to set the alpha channel of the color we're using when we draw the font. Color's arguments are in RGBA order, so 0,1,0,alpha is an opaque (or not) green. We're drawing the font to the middle of the screen, and this all looks like this when rendered:

Granted, it's small. Which we can fix. But it's a basic, simple screen to give our users feedback on how far along we are in loading any assets that we need. Not nearly as fun as something like showing a cute character running, or having some kind of nice screen transition. But hey, the class is called initial loading screen. So we can always expand out to more fun loading screens later! For now though, to modify the initial window size, we can go to the generated Lwjgl3Launcher class and modify the configuration:

public class Lwjgl3Launcher {
    public static void main(String[] args) {
        if (StartupHelper.startNewJvmIfRequired()) return; // This handles macOS support and helps on Windows.
        createApplication();
    }

    private static Lwjgl3Application createApplication() {
        return new Lwjgl3Application(new GameRunner(), getDefaultConfiguration());
    }

    private static Lwjgl3ApplicationConfiguration getDefaultConfiguration() {
        Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration();
        configuration.setTitle("Bustout");
        //// Vsync limits the frames per second to what your hardware can display, and helps eliminate
        //// screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard.
        configuration.useVsync(true);
        //// Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match fractional
        //// refresh rates. The Vsync setting above should limit the actual FPS to match the monitor.
        configuration.setForegroundFPS(Lwjgl3ApplicationConfiguration.getDisplayMode().refreshRate + 1);
        //// If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be
        //// useful for testing performance, but can also be very stressful to some hardware.
        //// You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing.

        configuration.setWindowedMode(640, 480);
        //// You can change these files; they are in lwjgl3/src/main/resources/ .
        //// They can also be loaded from the root of assets/ .
        configuration.setWindowIcon("libgdx128.png", "libgdx64.png", "libgdx32.png", "libgdx16.png");
        return configuration;
    }
}

The size we chose depends on the game we're making of course! 1280x800 would probably be good… Wait.

So, I realize that I haven't mentioned what game we're making yet! Well, I mean, the title of the post gives it away. But Break out is a fun game where you basically play pong with yourself. Except, instead of a wall to bounce the ball of loneliness against, we have a bunch of blocks that break upon being hit! The goal of course is to avoid letting the ball drop beneath your paddle, which runs horizontally across the screen. I played a version of this back on windows XP I think that was really really fun. I used to play it for hours because it had cool sound effects, neat power ups like multiple balls at once, or increased (or decreased!) size mechanics that were a lot of fun. The levels were shaped differently too, not always a single line of bricks to be broke, but other shapes and that sort of thing. 3

Basically, it was awesome. And also, we are in serious danger of feature and scope creep with this one! While making pong I intentionally allowed for drifting into ideas to apply and fiddle with patterns that may or may not be good ideas, this time we need to level up our software engineering time management skills if we're hoping to have a finished product at the end of this and not just a pile of code that's expanded in awkward places where it didn't need to. So, just like we did for Nyappy Bird we're going to heed my own advice and focus in on the important things first and then expand the game ideas where it makes sense to. I think it bears repeating, so let me quote myself quoting myself here:

Starting your game by creating assets before you make game logic is a surefire way to tank your productivity. All the time you spend tweaking pixels and visuals to get something "just" right, is time spent away from making your game mechanics fun.
- Peetseater

That said, we need something to show on the screen. So we have two options:

  1. Go to itch.io or similar and grab some free assets
  2. Use blocks of simple colors as placeholders

You might be inclined to go with option 1 because of course you want a good looking game right? But read my quote again. The spirit of this is that we don't get stuck hunting "the perfect" asset and instead: deal with that once the game is actually working. Looking on itch, even finding a really good asset pack with a lot of seemingly fitting things, it's a slippery slope and the moment you think: "Oh I need a background for this" or "I need to make a new screen for configuration" you're going to want to go do this again. The time spent tweaking compounds on itself in order to keep your game's feel consistent, and spending all that time upfront is a distraction.

Game feel is important, but it's best if we go for option 2 right now instead. That said, we can still hook this into the use of the asset manager so that we're future proofed in places where we'd just be using a simple texture anyway. When we eventually swap to loading files, we'll make tweaks to the asset descriptors but that should be it. 4

In my previous posts, especially the match 3 post from last year, I used the following code to create temporary textures:

public static Texture makeTexture(Color color) {
    int width = 64;
    int height = 64;
    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
    pixmap.setColor(color);
    pixmap.fill();
    Texture texture = new Texture(pixmap);
    pixmap.dispose();
    return texture;
}

This works, but is also done without the use of the asset manager, and so we're on the hook to call the texture.dispose() method ourselves as needed. Not really a problem with prototyping code, but what is a bit of a problem is that the static function ends up being littered throughout the codebase where we needed textures, normally in constructors or other similar places. So, when it comes time to refactor and add in real assets, we have more work to do than a simple tweak of a parameter in our AssetDescriptions. 5

Since I like the idea of having less work in the future, let's define a wrapper class for regular textures so that we can attach a custom loader to the asset manager:

package spare.peetseater.games.utilities;

import com.badlogic.gdx.graphics.Texture;

public class TemporaryTexture extends Texture {
    public TemporaryTexture(Texture texture) {
        super(texture.getTextureData());
    }
}

Truly revolutionary. I know! We're just making a simple wrapper class that the rest of the framework can see as a Texture. You might be asking why we need to do this. Well, the way that the asset manager in LibGDX works is by attaching loaders to classes, but it provides a number of built in loaders for the basic asset types that the library itself provides. There's a list of these in the wiki and that includes a TextureLoader which loads regular textures.

The temporary texture that makeTexture makes is, uh, a Texture! And since there's already a loader that exists for that, we can't hook in to make a texture on the fly within the asset manager. Ergo, we make a new class so that we can make a new loader that gives us the behavior we want. Asset loaders require two different type parameters, the asset type to load (the temporary class above) and the parameters needed to load it properly. For fonts that might be the desired size in pixels to generate for freetype, or shaders that might the path to the vertex or fragment file. In the case of our temporary textures, I'd like to be able to say what color they are, and potentially what size:

public static class TemporaryTextureParam extends AssetLoaderParameters<TemporaryTexture> {
    private final Color color;
    private final int width;
    private final int height;

    public TemporaryTextureParam(Color color, int width, int height) {
        this.color = color;
        this.width = width;
        this.height = height;
    }

    public TemporaryTextureParam(Color color) {
        this(color, 10, 10);
    }
}

There's not a lot to say about that, but now we can define the actual loader class. Since we're loading the data asynchronously, we can follow the same pattern as the PixmapLoader and define a loadSync method that does the work that we've done with our helper function before:

public class TemporaryTextureLoader extends AsynchronousAssetLoader<TemporaryTexture, TemporaryTextureLoader.TemporaryTextureParam> {
    public TemporaryTextureLoader(FileHandleResolver resolver) {
        super(resolver);
    }

    @Override
    public Array<AssetDescriptor> getDependencies(
        String fileName, FileHandle file, TemporaryTextureParam parameter
    ) {
        return null;
    }

    // The pixmap is loaded async, with no OpenGL thread available
    Pixmap pixmap;

    @Override
    public void loadAsync(
        AssetManager assetManager,
        String filename, 
        FileHandle file,
        TemporaryTextureParam parameter
    ) {
        pixmap = null;
        pixmap = new Pixmap(parameter.width, parameter.height, Pixmap.Format.RGBA8888);
        pixmap.setColor(parameter.color.cpy());
        pixmap.fill();
    }

    // The Open GL thread is available, so we can use new Texture()
    // and such without crashing
    @Override
    public TemporaryTexture loadSync(
        AssetManager manager,
        String fileName,
        FileHandle file,
        TemporaryTextureParam parameter
    ) {
        Pixmap pixmap = this.pixmap;
        Texture tmp = new Texture(pixmap);
        TemporaryTexture texture = new TemporaryTexture(tmp);
        pixmap.dispose();
        this.pixmap = null;
        tmp.dispose();
        return texture;
    }
}

the async vs sync part of the calls might be a little confusing, but essentially there are two threads. One thread is in the background not bothering anyone as it does whatever it needs to do (async) and the other thread is active when we're drawing the screen and have an actual open GL context loaded and ready to be used for anything one's heart desires. Which in our case is creating a texture in order to rip out the TextureData that the TemporaryTexture uses to construct itself. Trying to construct a Texture within the loadAsync method will crash the entire program, so don't do that.

This is a bit of boilerplate, but we can re-use this across projects and it fits in easily with our usage of the asset manager to load assets in the background. It also means we're not forcing users to keep every asset for the entire game in memory all the time! We can load, unload, and do whatever on the fly on a screen by screen basis to avoid cluttering up our players memory with stuff that no one cares about. In essense, all we have to do is create a descriptor:

public static final String PLAYER_ASSET_KEY = "player";
public static final AssetDescriptor<TemporaryTexture> PLAYER_PADDLE =
     new AssetDescriptor<>(
        PLAYER_ASSET_KEY,
        TemporaryTexture.class,
        new TemporaryTextureLoader.TemporaryTextureParam(Color.BLUE)
    );

and make sure our asset manager is made aware how to load these classes:

this.assetManager.setLoader(
    TemporaryTexture.class,
    new TemporaryTextureLoader(assetManager.getFileHandleResolver())
);

Then we can load it up and render it in any little scene we want to use as a test bed. So, we've got the one InitialLoadingScreen which purposefully avoids using any loaded assets so that it can be thrown up before anything else. So that's not really the spot to test this, so let's make a new screen!

public class LevelScreen extends ScreenAdapter {

    private final FitViewport viewport;
    private final Camera camera;
    private final GameRunner game;

    public LevelScreen(GameRunner game) {
        this.game = game;
        game.assetManager.load(GameAssets.PLAYER_PADDLE);
        camera = new OrthographicCamera(1280f, 800f);
        viewport = new FitViewport(1280, 800);
        viewport.setCamera(camera);
    }

    @Override
    public void render(float delta) {
        super.render(delta);
        game.batch.setProjectionMatrix(camera.combined);
        game.batch.begin();
        Texture texture = game.assetManager.get(GameAssets.PLAYER_PADDLE);
        game.batch.draw(
            texture,
            300, 300, 30, 30
        );
        game.batch.end();
    }
}

Now, this isn't how I like to configure the screens. In the last two games I introduced a Scene class that enables the rest of the game to make sure that an asset is loaded before trying to render it. The code above isn't set up like that, so if I were to naively try to do this from the GameRunner class:

setScreen(new LevelScreen(this));

We'd get a lovely error like this:

Exception in thread "main" 
    com.badlogic.gdx.utils.GdxRuntimeException: Asset not loaded: player
    at com.badlogic.gdx.assets.AssetManager.get(AssetManager.java:173)
    at com.badlogic.gdx.assets.AssetManager.get(AssetManager.java:181)
    at spare.peetseater.games.screens.LevelScreen.render(LevelScreen.java:30)

I personally would prefer if our game didn't do this. And if it would loads its assets in the correct order for any scenes it may want to show. I was thinking about this a little bit and came to a moderately amusing idea: could we define our scenes as assets themselves?

Maybe this isn't such a novel concept, since on second blush if one thinks about unity and other game engines it's not that unusual for everything to be an object that has behavior attached to it via composition (hello ECS), but the reason this intrigued me as a concept in general is this method that we stubbed out in our asset loader:

@Override
public Array<AssetDescriptor> getDependencies(
    String fileName,
    FileHandle file,
    TemporaryTextureParam parameter
) {
    return null;
}

If we crack open the source code for the framework and inspect the base class we can see the description:

Returns the assets this asset requires to be loaded first. This method may be called on a thread other than the GL thread.

This doesn't tell us a lot, but something I think is a hallmark of a good programmer is the willingness to embark into the unknown and explore. So, just as the wiki page on custom loaders suggests, we can explore the existing asset loaders for hints and tips on how they're used to find out if we should try do something like this. This recommend looking at BitmapFontLoader so that's what we'll do.

@Override
public Array<AssetDescriptor> getDependencies (
    String fileName,
    FileHandle file,
    BitmapFontParameter parameter
) {
    Array<AssetDescriptor> deps = new Array();
    if (parameter != null && parameter.bitmapFontData != null) {
        data = parameter.bitmapFontData;
        return deps;
    }

    data = new BitmapFontData(file, parameter != null && parameter.flip);
    if (parameter != null && parameter.atlasName != null) {
        deps.add(new AssetDescriptor(parameter.atlasName, TextureAtlas.class));
    } else {
        for (int i = 0; i < data.getImagePaths().length; i++) {
            String path = data.getImagePath(i);
            FileHandle resolved = resolve(path);

            TextureLoader.TextureParameter textureParams = new TextureLoader.TextureParameter();

            if (parameter != null) {
                textureParams.genMipMaps = parameter.genMipMaps;
                textureParams.minFilter = parameter.minFilter;
                textureParams.magFilter = parameter.magFilter;
            }

            AssetDescriptor descriptor = new AssetDescriptor(resolved, Texture.class, textureParams);
            deps.add(descriptor);
        }
    }

    return deps;
}

The key to understanding this is the BitmapFontParameter of course, which is in the same file, but I won't paste it here. The pattern that we can see from this framework code is that the way one specifies the dependencies should be based on something that can be determined based on the parameters sent along, or with the file one loads based on the initial asset dependency itself. So the question for us becomes if we could or should use that for this sort of thing.

The main argument against doing this is the fact that when we define scenes, they're also taking different context in order to render the right thing. For example, to pull from the previous post about the pong game. The screens we defined there had signatures of

public BattleScene(GameRunner gameRunner)
public TitleScreen(GameRunner gameRunner)
public WinScene(GameRunner gameRunner, String msg)
public GameModeSelectScene(GameRunner gameRunner)

Which, while almost all the same, differ for the win screen because it's given who actually won via a message to display. How would you deal with this for loading a generic Scene class? You have to call the constructor at some point, but how would you give it the message? A parameter? Then how do you take the superclass and call the right child constructor with the right thing? We could resolve dependencies via reflection perhaps, but this sounds a bit messy to have to do that, we could use something like Guice to tag things and define providers and all sorts of stuff like that maybe, but then you're not letting the asset loader asynchronously load your asset dependencies are you? We could define a provider as an asset loader parameter perhaps, but...

Based on the amount of hmmming and hummming this induces. I think that if we're not going to declare that all scenes only take the GameRunner or some other way of passing context and assets around, then we simply shouldn't do it. Another point against it is that when we define an asset loader, we want to load a class of asset types, not be forced into defining an asset loader for each type of scene. I'm still a little torn because it would be pretty nice to be able to transition between screens based on the asset being fulled loaded without having to define some extra code to check that, so maybe we can merge the ideas and make it possible to represent a scene's assets as something that needs to be loaded and then have that seperated from the scene itself?

In other words. Rather than us maintaining a list of assets in the constructor of the scene, we make a class that exists alongside it and that's intended to be given over to the asset manager when we want to load the screen. This might sound like the same thing, but I assure you my dear reader, it is not. Consider this code we've been using in other games:

// In an asset manager wrapper:
public void queueScene(Scene scene) {
    for (AssetDescriptor<?> assetDescriptor : scene.requiredAssets()) {
        assetManager.load(assetDescriptor);
    }
}

// in a scene
public PlayScreen(...) {
    ...
    assets = new LinkedList<>();
    assets.add(GameAssets.background);
    assets.add(GameAssets.multiplierSFX);
    assets.add(GameAssets.scoreSFX);
    assets.add(GameAssets.selectSFX);
    assets.add(GameAssets.negativeSFX);
    assets.add(GameAssets.sparkle);
    assets.add(GameAssets.tokens);
    assets.add(GameAssets.scoreFont);
    assets.add(GameAssets.bgm);
}

Compare this instead to something like this if we used the asset loader.

// In a scene
game.assetManager.load(GameAssets.MY_SCREEN_BUNDLE);

// Declared elsewhere:
public class MyScreenBundleParams extends SceneAssetBundleLoader.SceneAssetBundleParam {
    @Override
    public List<AssetDescriptor<?>> getDependencies() {
        this.dependencies.add(GameAssets.background);
        this.dependencies.add(GameAssets.multiplierSFX);
        this.dependencies.add(GameAssets.scoreSFX);
        this.dependencies.add(GameAssets.selectSFX);
        this.dependencies.add(GameAssets.negativeSFX);
        this.dependencies.add(GameAssets.sparkle);
        this.dependencies.add(GameAssets.tokens);
        this.dependencies.add(GameAssets.scoreFont);
        this.dependencies.add(GameAssets.bgm);
        return this.dependencies;
    }
}

My guess is that you feel like this right now:

But bear with me! One of these is better! The reason is simple. How many checks do we have to do to know if a scene is loaded in the first case? The second? In the first case we can query the sceen for its assets list, then do something like this

boolean allLoaded = true;
for (AssetDescriptor a : assets) {
    allLoaded = allLoaded && assetManager.isLoaded(a);
}

Which wastes time every frame if we're going to automate swapping from the loading and desired screen based on the state of our assets. "Memoize it then!" you cry, and that's exactly what the LibGDX asset manager does! So if we reduce the Scene's assets down to a 'bundle' asset, one which has dependencies that the asset manager tracks for us, then we only ever have to do one quick check in on the internal asset manager to make a decision! This is kind of nice! And if we define a scene interface like this:

public interface Scene {
    public String getBundleName();
    public Screen getScreen();
}

And update our loading screen code and level screen to implement them, then we can define the game loop like this:

public Scene loadingScreen;
public Scene desiredScreen;

@Override
public void create() {
    assetManager = new AssetManager();
    GameAssets.configure(assetManager);
    batch = new SpriteBatch();
    loadingScreen = new InitialLoadingScreen(this);
    desiredScreen = new LevelScreen(this);
    setScreen(loadingScreen.getScreen());
}

@Override
public void render() {
    if (!assetManager.isFinished()) {
        if (!getScreen().equals(loadingScreen.getScreen())) {
            setScreen(loadingScreen.getScreen());
        }
    }

    if (assetManager.isLoaded(desiredScreen.getBundleName())) {
        if (getScreen().equals(loadingScreen.getScreen())) {
            setScreen(desiredScreen.getScreen());
        }
    }
    super.render();
}

and then witness the screen boot up then swap automatically over to the new screen:

I intentionally added some sleeps and extra assets to make the loading screen take more than 0 seconds here, but yeah! It works! So, what does our fancy pants bundle class look like???

public class SceneAssetBundle {
    private final AssetManager assetManager;

    public SceneAssetBundle(AssetManager assetManager){
        this.assetManager = assetManager;
    }

    public <T> T get (AssetDescriptor<T> descriptor) {
        return this.assetManager.get(descriptor);
    }
}
...
public class LevelScreenBundleParams extends SceneAssetBundleLoader.SceneAssetBundleParam {
    @Override
    public List<AssetDescriptor<?>> getDependencies() {
        this.dependencies.add(GameAssets.PLAYER_PADDLE);
        return this.dependencies;
    }
}

This could honestly just be an empty class like public class SceneAssetBundle() with an empty constructor. But I wanted to be able to write code like this:

SceneAssetBundle bundle = game.assetManager.get(GameAssets.LEVEL_SCREEN_BUNDLE);
Texture texture = bundle.get(GameAssets.PLAYER_PADDLE);

Why? No real technical reason, I just think it feels nice to be able to say "get the asset from the bundle you said you cared about" rather than through game.assetManager.get. I suppose if I was bending backwards to think of something, you could insert some validations against requesting an asset that you didn't configure in your parameters. Then throw a nice exception that would defensively prevent you from doing anything unexpected. Like:

public SceneAssetBundle(AssetManager assetManager, SceneAssetBundleLoader.SceneAssetBundleParam parameter){
    this.assetManager = assetManager;
    this.parameter = parameter;
}

public <T> T get (AssetDescriptor<T> descriptor) {
    if (!this.parameter.getDependencies().contains(descriptor)) {
        throw new RuntimeException("OY DUMMY YOU DIDNT SPECIFY THAT");
    }
    return this.assetManager.get(descriptor);
}

But, I don't personally think this will be very useful to me. The existing asset manager runtime error for an unloaded asset is perfectly fine, and doing a contains check on every single get call to the bundle sounds like wasted CPU cycles and energy. If I was programming in rust or a C language, I could define the code to only be part of a debug build or something, but we're in Java and the existing framework works fine, so let's not. I'm really just doing bundle.get for style reasons.

Speaking of style. Similar to the pong clone. I'd like to break up the update and render calls like we did in that project because I like the distinction between state manipulation and visual draw calls that that provides. Before I do this though, I suppose I should show you the actual code for loading the SceneAssetBundle class. It's very similar to the temporary texture loader:

// Noting this import because Array is a gdx thing, nothing to do with 
// Java's []/ArrayLists.
import com.badlogic.gdx.utils.Array; 

public class SceneAssetBundleLoader extends AsynchronousAssetLoader<SceneAssetBundle, SceneAssetBundleLoader.SceneAssetBundleParam> {
    public static class SceneAssetBundleParam extends AssetLoaderParameters<SceneAssetBundle> {

        protected final List<AssetDescriptor<?>> dependencies;

        public SceneAssetBundleParam(List<AssetDescriptor<?>> dependencies) {
            this.dependencies = dependencies;
        }

        public SceneAssetBundleParam() {
            this(new ArrayList<>());
        }

        public List<AssetDescriptor<?>> getDependencies() {
            return dependencies;
        }
    }

    public SceneAssetBundleLoader(FileHandleResolver resolver) {
        super(resolver);
    }

    SceneAssetBundle bundle;
    @Override
    public void loadAsync(
        AssetManager assetManager,
        String filename,
        FileHandle file,
        SceneAssetBundleParam parameter
    ) {
        bundle = new SceneAssetBundle(assetManager);
    }

    @Override
    public SceneAssetBundle loadSync(
        AssetManager manager,
        String fileName,
        FileHandle file,
        SceneAssetBundleParam parameter
    ) {
        SceneAssetBundle tmp;
        tmp = bundle;
        this.bundle = null;
        return tmp;
    }

    @Override
    public Array<AssetDescriptor> getDependencies(
        String fileName,
        FileHandle file,
        SceneAssetBundleParam parameter
    ) {
        if (parameter == null) {
            return new Array<>();
        }

        Array<AssetDescriptor> array = new Array<>();
        for (AssetDescriptor a : parameter.getDependencies()) {
            array.add(a);
        }
        return array;
    }
}

We've got the definition of the SceneAssetBundleParam inside of the loader as a static class. This seems to be the way all the other asset loaders from the framework prefer to place these things, so I followed suit to fit in. There's nothing special about it beyond the fact that I made sure to have a constructor that doesn't take any arguments for stuff like the loader scene that has 0 dependencies. It's just a wrapper around a list.

The rest of the loader follows the pattern described by the GDX wiki, we set the instance field to null between the load sync and async calls as suggested, and our loadAsync method creates the "asset" here with the basic constructor. Simple! Let's do one more simple thing and then we'll get on with making the actual game.

public interface Scene {
    public String getBundleName();
    public void update(float seconds);
    public void render(float delta);
    public void dispose();
}

As said before the last snippet, I want to seperate the update and reder call out. But since we're loading the asset bundles too, I want to implement a dispose method too. Taking the level screen as an example since its pretty small. The change isn't too big, but there is a specific important thing I want to call out:

public class LevelScreen implements Scene {
    private final GameRunner game;

    public LevelScreen(GameRunner game) {
        this.game = game;
        Gdx.app.log(getClass().getSimpleName(), "LOAD: " + getBundleName());
        game.assetManager.load(GameAssets.LEVEL_SCREEN_BUNDLE);
    }

    @Override
    public void render(float delta) {
        ScreenUtils.clear(Color.BLACK);
        SceneAssetBundle bundle = game.assetManager.get(GameAssets.LEVEL_SCREEN_BUNDLE);
        Texture texture = bundle.get(GameAssets.PLAYER_PADDLE);
        game.batch.draw(
            texture,
            300, 300, 300, 300
        );
    }

    @Override
    public String getBundleName() {
        return GameAssets.LEVELSCREEN_BUNDLE_KEY;
    }

    @Override
    public void update(float seconds) {
        
    }

    @Override
    public void dispose() {
        Gdx.app.log(getClass().getSimpleName(), "UNLOAD: " + getBundleName());
        game.assetManager.unload(getBundleName());
    }
}

See how there's no game.batch.begin? This is on purpose because I'm also updating out GameRunner code a bit. For the variables at the top level and the creation, we'll use Scene rather than screen:

public static final float GAME_WIDTH = 1280f;
public static final float GAME_HEIGHT = 800f;
public SpriteBatch batch;
public AssetManager assetManager;
private Scene loadingScene;
private FitViewport viewport;
private OrthographicCamera camera;
public Scene currentScene;

@Override
public void create() {
    assetManager = new AssetManager();
    GameAssets.configure(assetManager);

    batch = new SpriteBatch();
    camera = new OrthographicCamera(GAME_WIDTH, GAME_HEIGHT);
    camera.setToOrtho(false, GAME_WIDTH, GAME_HEIGHT);
    viewport = new FitViewport(GAME_WIDTH, GAME_HEIGHT);
    viewport.setCamera(camera);

    loadingScene = new InitialLoadingScreen(this);
    currentScene = new LevelScreen(this);;
}

And tweaking the render method to now call the update and the render methods of the scene:

@Override
public void render() {
    if (Gdx.input.isKeyPressed(Input.Keys.ESCAPE)) {
        Gdx.app.exit();
        return;
    }

    float delta = Gdx.graphics.getDeltaTime();
    Scene scene;
    if (assetManager.isFinished()) {
        scene = currentScene;
    } else {
        scene = loadingScene;
    }

    scene.update(delta);
    camera.update();
    batch.setProjectionMatrix(camera.combined);
    batch.begin();
    scene.render(delta);
    batch.end();
}

Nothing too crazy, but I like pushing the camera and projection matrix up to this level. There's no reason to duplicate that code across the different scenes if all of them are always the same size and don't need to vary. If we were doing fun stuff like shifting perspectives, or zooming in and out, or anything fun like that, then sure, we might want to have different viewports across game components, but for now. One is fine! Let's call the setup done and move on!

Bouncing a ball

Ok. 25 minutes into your reading experience (as estimated by the reading time algorithm) we're going to actual start making code that relates directly to the breakout game. Hm. Sorry about that. 5

So, thinking about the game mechanics of break out. We need to bounce a ball around! This isn't terribly difficult. But one thing that we screwed up when we made pong was that the calculation for obstacle collision could end up overshooting and phasing through an obstacle by accident. You can see that a few times in my recordings like in this one at 19 seconds in:

The ball just goes right through and a point gets scored! Why does that happen? Because the calculation for velocity that we used in pong was something like this:

public void bounceOffOf(Paddle paddle) {
    ...
    v.x = dx * VIRTUAL_WIDTH;
    v.y = dy * VIRTUAL_WIDTH;
}

public void update(float delta) {
    x += v.x * delta;
    y += v.y * delta;
}

Where VIRTUAL_WIDTH was the screen size in game units! This, large step and the way we checked intersection I think contributed to obstacle detection code missing the mark on occasion. But, if that's the case, how do we fix it? Let's whip up some code to put the ball on the screen and then start tackling that, I've got an idea!

Before I can explain my idea though, here's my ball object:

public class Ball {
    private final Vector2 position;
    private final Vector2 velocity;
    private Vector2 dimensions;

    public Ball(Vector2 position, Vector2 velocity) {
        this.position = position.cpy();
        this.velocity = velocity.cpy();
        this.dimensions = new Vector2(10, 10);
    }

    public boolean willIntersect(Obstacle obstacle, float time) {
        return false;
    }

    public Vector2 getPosition() {
        return position.cpy();
    }

    public Vector2 getVelocity() {
        return velocity.cpy();
    }

    public Vector2 getDimensions() {
        return dimensions.cpy();
    }
}

The willIntersect code is just a stub implementation. We'll return to that in a minute, but for now we're just tracking the size, position, and dimensions of the ball 6. The only other thing of note is the usage of cpy.

I'm doing this because if you don't and were to pass the reference the engine gives you for stuff like Vector2.zero, you end up with weird spooky-action-at-a-distance type behavior. So, best to keep things immutable for the time being I think. Let's also define the obstacle our ball will run into:

public class Obstacle {
    private final Vector2 position;
    private final Vector2 dimensions;

    public Obstacle(Vector2 position, Vector2 dimensions) {
        this.position = position.cpy();
        this.dimensions = dimensions.cpy();
    }

    /** It is assumed that intersection has been computed if we are using this */
    public float getBounceAngle(Ball ball) {
        ...
    }

    public Vector2 getPosition() {
        return position.cpy();
    }

    public Vector2 getDimensions() {
        return dimensions.cpy();
    }
}

I've elided the bounce angle function for the time being so that it's easier for you to see the properties of the obstacle. Just like the ball, we're making copies of the vectors so that we don't get any weird mutated stuff happening if we pass the position or dimension around. We might want that sort of state change later if we do power-ups, but for the time being let's not.

We've got a ball and obstacles. So then let's talk about the level itself. Tim Cain had a video about keeping engines and game seperate that was interesting and had me thinking about how troublesome it is to test things sometimes. I mean, as I noted last time, it's a bit of a pain to get JUnit into place for this code sometimes, but I want to try to take a page from that book for us to fix the bug we're talking about. So, let's define the level as a class too and test it outside of the context of the scene!

public class Level {
    private final Obstacle player;
    private final List<Obstacle> obstacles;
    private final Ball ball;
    private final float ballSpeed;

    public Level(Obstacle player, List<Obstacle> obstacles, float ballSpeed) {
        this.player = player;
        this.ball = new Ball(
            this.player.getPosition()
                .add(player.getDimensions().x/2f, player.getDimensions().y)
            , Vector2.Zero
        );
        this.obstacles = obstacles;
        this.ballSpeed = ballSpeed;
    }

    public Ball getBall() {
        return ball;
    }

    public List<Obstacle> getObstacles() {
        return obstacles;
    }

    public Obstacle getPlayer() {
        return player;
    }
}

With these classes defined, we can construct a happy little level in our LevelScreen class.

public LevelScreen(GameRunner game) {
    this.game = game;
    List<Obstacle> obstacles = new LinkedList<>();
    Vector2 brickSize = new Vector2(150, 50);
    for (float x = 50; x < GAME_WIDTH - 50; x += brickSize.x + 25) {
        for (
            float y = GAME_HEIGHT - 400;
            y < GAME_HEIGHT - brickSize.y + 10;
            y += brickSize.y + 10
        ) {
            obstacles.add(new Obstacle(new Vector2(x, y), brickSize));
        }
    }
    Vector2 playerPosition = new Vector2(GAME_WIDTH / 2f - 80, 25);
    Vector2 playerSize = new Vector2(160, 45);
    Obstacle player = new Obstacle(playerPosition, playerSize);
    this.level = new Level(player, obstacles, 50); // 50 is arbitrary for now
    Gdx.app.log(getClass().getSimpleName(), "LOAD: " + getBundleName());
    game.assetManager.load(GameAssets.LEVEL_SCREEN_BUNDLE);
}

And then, for each type of object, we just render it based on the two vectors that represent the position and dimensions. For example, the player paddle render code is just:

Texture playerTexture = bundle.get(GameAssets.PLAYER_PADDLE);

float px = level.getPlayer().getPosition().x;
float py = level.getPlayer().getPosition().y;
float pw = level.getPlayer().getDimensions().x;
float ph = level.getPlayer().getDimensions().y;
game.batch.draw(
    playerTexture,
    px, py, pw, ph
);

Once we've done this for the ball and obstacles as well, then we get a screen that sort of kind of looks like a break out game:

The actual placement doesn't really matter much, and I want to eventually load those from a file so that we can fiddle and define a bunch of levels really easily. But for now, it's just a proof that I've calculated the ball's starting position correctly. Let's get onto the bouncing! In order to bounce, we need to know that we're hitting the obstacle in our way! For example, if the yellow ball is in the middle of some obstacles, and is moving with a given velocity, then how do we figure out that within 3 ticks of time we'll hit it?

With the above picture it seems like the usual check on each time step would work. But, what about if the amount of time is bigger? Like there's a bit of lag or something similar and so GDX calls our render code with a decent chunk of delta time and this happens?

Discrete time intervals are what leads to the issue we saw before. So then the obvious idea to fix it is to consider the result continuously! To do that, I think we're back to the ever old math question. Do these lines intersect? And then the question of: do they intersect within the given range? Let's formulate our questions into a set of unit tests:

class BallTest {

    @org.junit.jupiter.api.Test
    void willNotIntersectWith0SpeedNotTouching() {
        Vector2 bp = new Vector2(1, 1);
        Vector2 bv = Vector2.Zero;
        Ball ball = new Ball(bp, bv); // Balls are 10x10 units big

        Vector2 op = new Vector2(12, 12);
        Vector2 od = new Vector2(2, 2);
        Obstacle obstacle = new Obstacle(op, od);

        assertFalse(ball.willIntersect(obstacle, 10));
    }

    @org.junit.jupiter.api.Test
    void willIntersectWith0SpeedTouching() {
        Vector2 bp = new Vector2(1, 1);
        Vector2 bv = Vector2.Zero;
        Ball ball = new Ball(bp, bv);

        Vector2 op = new Vector2(1, 11);
        Vector2 od = new Vector2(2, 3);
        Obstacle obstacle = new Obstacle(op, od);

        assertTrue(ball.willIntersect(obstacle, 0));
    }

    @ParameterizedTest
    @MethodSource("intersectCases")
    void willIntersect(TestCase testCase) {
        boolean willIntersect = testCase.ball.willIntersect(testCase.obstacle, 2f);
        assertTrue(willIntersect, testCase);
    }
    ...
}

The first two are special cases I want to make sure we solve for, when there is 0 velocity at all for the ball! If the ball isn't moving, then it should never intersect something its not already touching. Likewise, if the ball is already touching an obstacle but isn't moving, then it's still intersecting. Right? I figure it makes sense to make sure we handle that edge even if our ball will have a velocity most of the time. Now, that last test. That's our quest giver right there. Parameterized tests are a wonderful thing because we have a lot of potential situations to check on, but all of them should be intersecting.

So what does a TestCase look like?

// Ball is at 10,10 and dimensions are 10x10
// Velocity is settable and there is an obstacle with dimenions 5x5 at the given position.
static class TestCase implements Supplier<String> {
    private final Ball ball;
    private final Obstacle obstacle;
    Vector2 velocity;
    public TestCase(Vector2 ballVelocity, Vector2 obstaclePosition) {
        this.velocity = ballVelocity;
        this.ball = new Ball(new Vector2(10, 10), ballVelocity.cpy());
        this.obstacle = new Obstacle(obstaclePosition, new Vector2(5, 5));
    }

    public static TestCase setup(float vx, float vy, float ox, float oy) {
        return new TestCase(new Vector2(vx, vy), new Vector2(ox, oy));
    }

    public Ball getBall() {
        return this.ball;
    }

    public Obstacle getObstacle() {
        return this.obstacle;
    }

    @Override
    public String get() {
        return String.format("10x10 Ball at %f,%f, 5x5 Obstacle at %f, %f, velocity of %f,%f",
            ball.getPosition().x, ball.getPosition().y,
            obstacle.getPosition().x, obstacle.getPosition().y,
            velocity.x, velocity.y
        );
    }
}

I'm somewhat regretting making our ball fixed at a 10x10 size, but not regretting it enough to change it. Each test case can be defined by the velocity of the ball and where the obstacle is. The Supplier interface is used to make our unit tests actually comprehensible when they fail from the parameterized runs and we want to actually see what the heck was going on so we can figure out if our code is wrong, or the test scenario is wrong. The test scenarios are all defined by the intersectCases method:

static Stream<TestCase> intersectCases() {
    // Ball is at 10,10
    // velocity bx,by and obstacle position ox, oy are set up.
    // velocities are configured to go past the obstacle in 1 time unit
    return Stream.of(
        TestCase.setup( 6,  0, 22, 10 ), // to the right
        TestCase.setup(-6,  0,  6, 10 ), // to the left
        TestCase.setup( 0,  7, 10, 22 ), // directly above
        TestCase.setup( 0, -7, 15, 4  ), // directly below
        TestCase.setup( 6,  6, 21, 21 ), // diagonally to the up right
        TestCase.setup(-6,  6,  4, 25 ), // diagonally to the up left
        TestCase.setup( 6, -6, 22, 7  ), // diagonally to the down right
        TestCase.setup(-6, -6,  4, 7  )  // diagonally to the down left
    );
}

I figure if I try to cover the cardinal directions, and configure the velocity so that it's always overshooting the obstacle, that we'll be able to resolve that pong bug from before without having to worry about the fact that in the break out game, the ball is going to bounce around and hit every side of the obstacles as it moves around the stage. Assuming I haven't visualized the coordinates in my head wrong, the 5x5 obstacle should get hit by the ball in each. But of course, since we're hardcoding false right now in the stub, everything fails!

But like I said, the test case running under ParameterizedTest is our quest giver! And we just got 10 new items in our journal! I stared at the code for a bit, thought about the ol' y = mx + b line equations for a while and then was chatting with some folks when I got directed to this interesting trick. It's a short post if you want to read it, but here's a visual on how this works:

The red line segment AB intersects the blue line segment CD if the line drawn from ACD is counterclockwise and the line drawn from BCD is clockwise, then that means the lines approached the line from opposite sides, which means that the segment was between A and B.

Similar, if you look at the other case, where the segments don't intersect. You can see that ACD is counterclockwise, but so is BCD, meaning we approached CD from the same direction from both A and B. This is pretty good. But it isn't perfect:

The problem is noted in the comments of that blogpost and are also presented by the failed test cases as well:

10x10 Ball at 10.0,10.0, 5x5 Obstacle at 6.0, 10.0, velocity of -6.0,0.0
10x10 Ball at 10.0,10.0, 5x5 Obstacle at 10.0, 22.0, velocity of 0.0,7.0
10x10 Ball at 10.0,10.0, 5x5 Obstacle at 15.0, 4.0, velocity of 0.0,-7.0

Notice the similarity here? All the problems occur in cases where the velocity is unidimensional and the obstacle and ball have aligned with each other. For the record since it's easy to miss, the test code is passing 2f for the time step in all these parameterized cases. So in each case the ball ends up at:

  1. -2,10
  2. 10,24
  3. 10,-4

So in each case, there's collision between the two objects, and yet, our test fails. Why? Let's draw it out. The red is our ball and the black is where the is at t=2. Blue is the obstacle in our way that we're phasing into.

The case I described about ACD and BCD both check out. But look at the green lines. CAB is a flat line because C lies directly on the segment. So we can't compare whether or not CAB and DAB have opposite rotations to prove out that both segments intersect each other. Because a straight line has no rotation.

So how do we get these weirdo cases to pass? Well, given that we'll we have simple geometry and only rectangles both in our test objects, and in our actual game. We probably don't need to consider the cases like when this sort of things happen at an angle. So we can consider just the special cases of the horizontal and vertical lines. Detecting vertical and horizontal lines isn't particularly hard to do, the question just becomes what to do once we know they exist?

Well, my first thought is that we could use the equation of the line for our ball's start and end point, compute its y-intercept, and try to figure out if there's a way to morph that onto our obstacle. Basically, try to compute the intersection point and then I guess check to see if said point is between the boundaries of a line, but will that actually work? Will we be at rist of overshooting again?

My second thought is motivated by the amount of time I was sitting at my keyboard, hand on my chin as my eyelids drooped lower and lower as my mood dipped into "wow you're a failure" mode as what feels like basic math slipped from my brain once more.

But the second thought isn't actually as bad as the mood itself though. I was just thinking that maybe I toss all of this out, go back to computing overlapping boxes with my old method, and just make the time step fixed. Even if libgdx hands me something like 3 seconds between frames (it wont, but pretend), I can just 'tick' 3 times instead of once to gradually move the update along. If I ensure that the ticking step is small enough that the velocity of the ball won't overshoot an obstacle it might touch, then I'll have sidestepped the entire problem of solving line equations! This does mean that the cpu is doing more work per frame, but I'm not sure if that's actually a problem. Like, what does the processing between checking two boxes for overlap 3 times compare to one big line equation solve?

To get a feel for the answer to that question, let me actually reveal some code to you on what's been passing those 5 unit tests. First, the adapted code from the blog post I linked to:

/*
 * https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
 */
private boolean counterclockwise(Vector2 a, Vector2 b, Vector2 c) {
    return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}

private boolean intersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d) {
    return counterclockwise(a,c,d) != counterclockwise(b,c,d) && 
           counterclockwise(a,b,c) != counterclockwise(a,b,d);
}

And then, since I'm not working with lines, but shapes, the computation of each corner through the 4 walls of the obstacle:

public boolean willIntersect(Obstacle obstacle, float time) {
    // Our expected line segment from the ball's anchor to the anchor at time t.
    // we compute this from each corner through the walls of the obstacle
    float bw = getDimensions().x;
    float bh = getDimensions().y;
    Vector2[] corners = {
      getPosition(),
      getPosition().add(0, bh),
      getPosition().add(bw, 0 ),
      getPosition().add(getDimensions())
    };

    float ow = obstacle.getDimensions().x;
    float oh = obstacle.getDimensions().y;

    for (int i = 0; i < corners.length; i++) {
        Vector2 ballStart = corners[i];

        float endX = ballStart.x + velocity.x * time;
        float endY = ballStart.y + velocity.y * time;
        Vector2 ballEnd = new Vector2(endX, endY);

        // Ball XY to End XY is now a line segment that we need to check if it
        // passes through any of the walls of the obstacle.
        boolean intersectsBottom = intersect(
            ballStart, ballEnd, 
            obstacle.getPosition(), obstacle.getPosition().add(ow, 0)
        );
        boolean intersectsLeftWall = intersect(
            ballStart, ballEnd, 
            obstacle.getPosition(), obstacle.getPosition().add(0, oh)
        );
        boolean intersectsTop = intersect(
            ballStart, ballEnd,
            obstacle.getPosition().add(0, oh), obstacle.getPosition().add(ow, oh)
        );
        boolean intersectsRightWall = intersect(
            ballStart, ballEnd,
            obstacle.getPosition().add(ow, 0), obstacle.getPosition().add(ow, oh)
        );
        if ( intersectsBottom || intersectsLeftWall || intersectsTop || intersectsRightWall) {
            return true;
        }
    }

    // no intersection detected through the walls, but we 
    // need to check if the lines are flat. TODO!
    return false;
}

Yeah. That is indeed a for loop you're seeing. And yes, we are checking every single corner of the ball to see if it passed through every single wall of the obstacle. That's a lot of math! And technically, we're not even done yet because we haven't dealt with the whole vertical line thing I talked about. Consider the previous intersection code from pong or nyappy cat:

public boolean intersects(Ball ball) {
    // For the time being, proper square check
    float pLeftX = ball.getAnchorX();
    float pLeftY = ball.getAnchorY();
    float pRightX = pLeftX + Ball.CIRCUMFERENCE;
    float pRightY = pLeftY + Ball.CIRCUMFERENCE;

    boolean overlapsXProjection = pRightX >= x && (x + WIDTH) >= pLeftX;
    boolean overlapsYProjection = pRightY >= y && (y + HEIGHT) >= pLeftY;
    return overlapsXProjection && overlapsYProjection;
}

That's a lot simpler. Probably faster too. And if I did that 3 times in one frame (again, we haven't chosen a time step yet so I don't actually know how many repetitions we'd need) that would probably still be less work than the current counterclockwise rotation type thing. Thinking about physics. We know that if d = v * T then we can also say that t = d/v, and so if we want to ensure that the precision of our movement is only d with some velocity v then we can compute the time step we want to always use!

So, assuming we'll allow for say, 2 units off, then t = 2/v, and in the case of a velocity of -6 like in the first test, the timestep then would be t = 2/-6 = 1/-3 which is... negative. I suppose we can just absolute value it because time isn't negative. So then how many steps does it take to do 2 seconds with a step of 1/3? Well... 6 obvious. So then, is doing the intersects method 6 times better or worse than the counterclockwise code?

The answer feels like an obvious "better" since if we count up the number of operators it's 11 x vs 10 * 4 + the operators to get the line segments. But the age old question is if we're cleverly solving the problem in a different way, or just trying avoid doing the right thing because it feels hard?

So. Before I do what feels like accepting defeat. What if we think about the old and new locations as part of one continuous rectangle? Well, this'll work in the case where we move horizontally and vertically, just look at the picture!

But as you can see. If we were moving diagonally, that doesn't quite work does it. We'd get a false positive. But, as noted before, our counterclockwise check works when we move diagonally. So, what if… we combine these two ideas? Well, if I make a quick little helper like this:

public Ball projectedAt(float time) {
    Vector2 p = getPosition().add(getVelocity().scl(time));
    return new Ball(p, getVelocity());
}

And then transfer the pong intersect code over to take in an obstacle, then I can write a couple quick if statements before we do that big for loop related thing I showed you above to check at the location we were and where we are now:

public boolean willIntersect(Obstacle obstacle, float time) {
    // Fast check on if we are currently bonking something
    if (intersects(obstacle)) {
        return true;
    }
    
    // Fast check again on expected position
    Ball nextPosition = this.projectedAt(time);
    if (nextPosition.intersects(obstacle)) {
        return true;
    }
    
    // And finally, the hard part. Dealing with overshooting
    // and landing on a position that is no longer intersecting
    // with the obstacle itself.
    // Our expected line segment from the ball's anchor to the anchor at time t.
    // we compute this from each corner through the walls of the obstacle
    float bw = getDimensions().x;
    float bh = getDimensions().y;
    ... the code from before ...

I'm not checking the BIG rectangle I noted would have problems with diagonal movement yet, but I'm just curious, our current tests, how are we fairing compared to before?

Hey that's… weird. I could have sworn the tests I had included a pure overshoot! But I guess not, now that I look twice. The ones I was thinking about were where we hopped over 7 units like TestCase.setup(0, 7, 10, 22), but in this case there's still overlap since the obstacle stretches from (10, 22) to (15, 27) and the ball lands at (10,24) so it was a direct overlap. Adding in this case

TestCase.setup(-20, 0, 0, 8)

Gets the expected result that shows that we do indeed still have some work to do:

Also as expected though, the counterclosewise code does handle this overshoot as you can see with this test case:

TestCase.setup(-20, -20, 0, 0) // overshoot diagonally

So then! To make the tests pass, all I need to do is check the slope, or maybe just check if one of the velocity components is 0 and then extend the box in that direction and run the intersect. Let's see if this works:

// If we are moving horizontally or vertically we can extend the box
// and do a basic intersect. this will be faster than the counterclosewise
// check we have to do otherwise.
if (velocity.x == 0 || velocity.y == 0) {
    float x = Math.min(position.x, nextPosition.position.x);
    float y = Math.min(position.y, nextPosition.position.y);
    float w = Math.abs(position.x - nextPosition.position.x) + dimensions.x;
    float h = Math.abs(position.y - nextPosition.position.y) + dimensions.y;
    Ball spaghettification = new Ball(new Vector2(x, y), Vector2.Zero, new Vector2(w, h));
    if (spaghettification.intersects(obstacle)) return true;
}

To get this working I had to add in a new constructor to the ball since the dimensions were fixed. But these types of things are pretty simple. When you need to do this, it's always easy to preserve the interface to what came before via an overload:

public Ball(Vector2 position, Vector2 velocity, Vector2 dimensions) {
    this.position = position.cpy();
    this.velocity = velocity.cpy();
    this.dimensions = dimensions.cpy();
}

public Ball(Vector2 position, Vector2 velocity) {
    this(position, velocity, new Vector2(10, 10));
}

Then no other code has to change, but we get more flexibility. And with that done, we can see that our tests all pass now:

Great! So, if we can detect that a ball is colliding with something, then we can make the ball bounce off of whatever that something is! So, I started in our Level class typing up an update function

public void update(float delta) {
    boolean ballHitSomething = false;
    for (Obstacle obstacle : obstacles) {
        if (this.ball.willIntersect(obstacle, delta)) {
            float radians = obstacle.getBounceAngle(ball);
            // wait a sec...            
        }
    }
}

But then paused, what happens if I hit more than obstacle at once? Is that possible? No, right? If I bonk against one brick in a frame, I shouldn't be able to hit another since we'll bounce? As long as we bounce, even if we were sliding at a super fine angle like 179 degrees or something, we'd still bounce off and get a new angle for a hit later on. I think. I suppose we'll see if anything wacky happens if we were to set up bricks with enough space for a ball to bounce between them but for everything to be super tight and happen really fast.

Anyway, idle musing that pauses my thoughts aside, I don't think I've actually shown you the bounce angle function yet! We could probably write this code in a smaller way, but I like that it feels clear to me.

/** It is assumed that intersection has been computed if we are using this */
public float getBounceAngle(Ball ball) {
    // use center point for bounce determination!
    float bx = ball.getPosition().x + ball.getDimensions().x/2f;
    float by = ball.getPosition().y + ball.getDimensions().y/2f;

    float ox = position.x + dimensions.x /2f;
    float oy = position.y + dimensions.y /2f;

    boolean ballToTheLeft   = bx < ox;
    boolean ballUnderneath  = by < oy;
    boolean ballToTheRight = !ballToTheLeft;

    // Off to the quadrant you go
    float angle1;
    float angle2;
    if (ballToTheLeft && ballUnderneath) {
        // 180 - 270 (but nix the edge, we don't want purely horizontal/vertical movement ever.
        angle1 = 181;
        angle2 = 269;
    } else if (ballToTheLeft) {
        // 90 - 180
        angle1 = 91;
        angle2 = 179;
    } else if (ballToTheRight && ballUnderneath) {
        // 270 - 360
        angle1 = 271;
        angle2 = 359;
    } else {
        // 0 - 90
        angle1 = 1;
        angle2 = 89;
    }

    float radA = MathUtils.degreesToRadians * angle1;
    float radB = MathUtils.degreesToRadians * angle2;
    return MathUtils.lerpAngle(radA, radB, MathUtils.random());
}

We use the center point of the ball and the center point of the obstacle in order to construct a pseudo unit "rectangle", which is like a unit circle, but all our obstacles are rectangles, so this makes more sense to visualize when we're thinking about the quadrants that we could bounce off of:

If the centerpoint is in the top left, we bounce off at a random angle in the top left, similar for all the other quadrants. This felt like a good idea, but in actuality, random angles can give rise to weird situations like this:

So! Let's change this code to normalize the bounce angle according to where the ball hits the obstacle. 7 I experimented with a few techniques, got frustrated by math (as per usual it seems), and then ended up with the following idea. Why don't I just avoid all this math that keeps frustrating me by doing simple vector arithmetic and a reflection of the current ball velocity?

In the case of a non-moving obstacle, the ball will simply flip direction and contine on. In the case of the player paddle though? If you're moving to the side when you touch the ball, then your velocity is added to the ball. Which means you can speed or slow the ball by moving when contact happens. I like this idea a lot since I think that this dodges the question of needing to calculate an angle at all. 8 It's also dead simple code wise:

Vector2 ov = obstacle.getVelocity();
Vector2 v = this.ball.getVelocity().scl(-1).add(ov);
this.ball.setVelocity(v);
this.ball.update(delta);                    

And this, though it might look a little funny, looks like this when obstacles are completely stationary:

This is a bit off, as even if I'm computing an angle, I still want to tweak the way the ball bounces. Like, it would feel way more natural if the ball bounced off to the left in the video above than the right. So, let's do some simple maths to figure out which direction we want the ball to bounce:

If we decide the way the ball reflects to be based on where it hits the obstacle then we can use simple subtraction to determine the sign that the velocity vector should have. Then, it's just a matter of application:

private void collideBallWithObstacle(Obstacle obstacle, float delta) {
    Vector2 ov = obstacle.getVelocity();
    Vector2 reflection = ball.getCenter().sub(obstacle.getCenter());
    reflection.x = Math.signum(reflection.x);
    reflection.y = Math.signum(reflection.y);
    Vector2 bv = this.ball.getVelocity();
    bv.x = Math.abs(bv.x);
    bv.y = Math.abs(bv.y);
    Vector2 v = bv.scl(reflection).add(ov);
    this.ball.setVelocity(v);
    this.ball.update(delta);
}

Is this perfect? No. I'm not really satisfied still, but for the sake of making progress, let's say that this is okay for now and move on. We've got other aspects of breakout to handle! The behavior from the previous video still holds if the player paddle is positioned where it was. This is because it's still hitting the right hand side of the top of the paddle, and therefore the reflection gets a positive x and y direction. We'll return to this later, once my ego has recovered from another kick from the mathematic demons.

That said, before we move on, let's circle back to our update function. Now that we have our collide function, we can use it:

public void update(float delta) {
    this.player.update(delta);

    boolean ballHitSomething = false;
    for (Obstacle obstacle : obstacles) {
        if (this.ball.willIntersect(obstacle, delta)) {
            collideBallWithObstacle(obstacle, delta);
            ballHitSomething = true;
            break;
        }
    }

    // Paddle collisions
    if (this.ball.willIntersect(player, delta)) {
        collideBallWithObstacle(player, delta);
        ballHitSomething = true;
    }

    if (!ballHitSomething) {
        this.ball.update(delta);
    }
}

I don't think there's too much to say here beyond the fact that I think it's a good idea to update the player first That way if someone was scooting along, holding the move button and praying they'd make it in time, we give them the best chance to do so. Since the collideBallWithObstacle function calls this.ball.update, we only call it from the Level's update method if we didn't hit anything at all.

Obstacles break!

In order for break-out to be, well, break out. You have to break the obstacles! This is a really really straightforward piece of code to add into our existing update method. We just need to keep a track of any obstacle we collide with and then remove it! Since the ball can only hit one obstacle per update (since we call break in the obstacle loop) we don't even need to construct a list!

Obstacle toRemove = null;
for (Obstacle obstacle : obstacles) {
    if (this.ball.willIntersect(obstacle, delta)) {
        collideBallWithObstacle(obstacle, delta);
        toRemove = obstacle;
        ballHitSomething = true;
        break;
    }
}
if (toRemove != null) {
    obstacles.remove(toRemove);
}

Yup! And now the game feels more like breakout than ever:

There done. Game is done.

Ok fine, it's not. But when there's something actually reacting to the ball colliding, it definitely feels more like we're making progress, doesn't it? Granted, there's not really much point in breaking things if you're not being rewarded for it, right? So, we need to keep track of a score whenever we break something, and that's pretty dang simple, if we wanted to not make any new classes, we could just toss in an int into our level:

public class Level {
    ...
    private int score;

    public Level(Obstacle player, List<Obstacle> obstacles, float ballSpeed) {
        this.score = 0;
        ...
    }
    ...
}

And in the same place we check for collision, we can increment it. But! How much do we increment it by? My first instinct was 1, but then I was reading the breakout wikipedia page and it informed me:

Yellow bricks earn one point each, green bricks earn three points, orange bricks earn five points and the top-level red bricks score seven points each.

So, the points are based off of color! When we constructed our "happy little level" above, I wasn't really thinking too much about this type of thing. But it does sound like we'll need to figure this out in order to score properly. So, what's the level supposed to be like:

Breakout begins with eight rows of bricks, with two rows each of a different color. The color order from the bottom up is yellow, green, orange and red.

So I suppose the question becomes, how do we want to assign points to the obstacles? It seems a bit much to assign subclasses for just one value like this changing. Though, I do remember the breakout clone I played as a kid would take more than one hit to break through some obstacles. That could be a fun thing to do, but either way, I don't think I need to subclass. So instead, let's create a property for scoring and set it when we define our level.

public class Obstacle {
    ...
    private final int worth;

    public Obstacle(Vector2 position, Vector2 dimensions) {
        this(position, dimensions, Vector2.Zero);
    }

    public Obstacle(Vector2 position, Vector2 dimensions, Vector2 velocity) {
        this(position, dimensions, velocity, 0);
    }

    public Obstacle(Vector2 position, Vector2 dimensions, Vector2 velocity, int worth) {
        this.position = position.cpy();
        this.dimensions = dimensions.cpy();
        this.velocity = velocity.cpy();
        this.worth = worth;
    }

    static Obstacle RedBrick(Vector2 position, Vector2 dimensions) {
        return new Obstacle(position, dimensions, Vector2.Zero, 7);
    }

    static Obstacle OrangeBrick(Vector2 position, Vector2 dimensions) {
        return new Obstacle(position, dimensions, Vector2.Zero, 5);
    }

    static Obstacle GreenBrick(Vector2 position, Vector2 dimensions) {
        return new Obstacle(position, dimensions, Vector2.Zero, 3);
    }

    static Obstacle YellowBrick(Vector2 position, Vector2 dimensions) {
        return new Obstacle(position, dimensions, Vector2.Zero, 1);
    }
    ...

There. Now we've got a bunch of handy constructors and after I make a getter function for the property, we'll be able to update our level code really easily:

for (Obstacle obstacle : obstacles) {
    if (this.ball.willIntersect(obstacle, delta)) {
        collideBallWithObstacle(obstacle, delta);
        score += obstacle.getWorth();
        ...
    }
}

Of course, we won't get any score at all yet because our obstacle generation code is using the constructor that provides 0 worth. So, let's revisit that code! We've got:

Vector2 brickSize = new Vector2(150, 50);
for (float x = 50; x < GAME_WIDTH - 50; x += brickSize.x + 25) {
    for (float y = GAME_HEIGHT - 400; y < GAME_HEIGHT - brickSize.y + 10; y += brickSize.y + 10) {
        obstacles.add(new Obstacle(new Vector2(x, y), brickSize));
    }
}

Yup. That sure is hacky, get something on the screen code. It's like we only wrote it a few minutes ago or something to get something on the screen! But now we actually know how the obstacles are supposed to be laid out! 8 rows tall in an 800 unit tall window? Great! How about we tweak the obstacle sizes a bit annnnd:

Vector2 brickSize = new Vector2(150, 30);
for (float x = 50; x < GAME_WIDTH - 50; x += brickSize.x + 25) {
    float padding = 15 + brickSize.y ;
    obstacles.add(Obstacle.RedBrick(new Vector2(x, 800 - padding), brickSize));
    obstacles.add(Obstacle.RedBrick(new Vector2(x, 750 - padding), brickSize));
    obstacles.add(Obstacle.OrangeBrick(new Vector2(x, 700 - padding), brickSize));
    obstacles.add(Obstacle.OrangeBrick(new Vector2(x, 650 - padding), brickSize));
    obstacles.add(Obstacle.GreenBrick(new Vector2(x, 600 - padding), brickSize));
    obstacles.add(Obstacle.GreenBrick(new Vector2(x, 550 - padding), brickSize));
    obstacles.add(Obstacle.YellowBrick(new Vector2(x, 500 - padding), brickSize));
    obstacles.add(Obstacle.YellowBrick(new Vector2(x, 450 - padding), brickSize));
}

We've called these red, orange, green, and yellow but they're all uhm. Green. So, let's change that! Since the value of the score is what actually determines what each one should be, then we can just check that during the render step of the level screen. Our previous code is just looping over the obstacle and using a single texture to write everything out:

for (Obstacle obstacle : level.getObstacles()) {
    float ox = obstacle.getPosition().x;
    float oy = obstacle.getPosition().y;
    float ow = obstacle.getDimensions().x;
    float oh = obstacle.getDimensions().y;
    game.batch.draw(
        obstacleTexture,
        ox, oy, ow, oh
    );
}

The wikipedia page mentioned a fun little fact:

The arcade cabinet uses a black and white monitor, but the monitor has strips of colored cellophane placed over it so that the bricks appear to be in color.

Which kind of made me think of just having a single white texture + a shader to set the color based on the position. But compiling a shader for that when we could just load up the color itself seems a bit odd. Like, in the physical world, white + colored tape does seem like a cute and clever solution to limitations of a display. But in software world, it sounds like it'd just be wasteful? Maybe it's not, I'm not familiar enough with GPUs to know if 1 texture + a shader to get 4 colors out of it is actually better or not. I just wanted to share the fun fact! For now, we'll take the easy path and just declare a one more block asset (we already made the others):

public static final AssetDescriptor<TemporaryTexture> ORANGE_SQUARE = new AssetDescriptor<>(
    "orangesquare",
    TemporaryTexture.class,
    new TemporaryTextureLoader.TemporaryTextureParam(Color.ORANGE)
);

Then after we update the level bundle to say that this screen depends on those, we can tweak the obstacle drawing code:

- Texture obstacleTexture = bundle.get(GameAssets.GREEN_SQUARE);
Texture greenBrickTexture = bundle.get(GameAssets.GREEN_SQUARE);
Texture redBrickTexture   = bundle.get(GameAssets.RED_SQUARE);
Texture orangeBrickTexture = bundle.get(GameAssets.ORANGE_SQUARE);
Texture yellowBrickTexture = bundle.get(GameAssets.YELLOW_SQUARE);
...
for (Obstacle obstacle : level.getObstacles()) {
    float ox = obstacle.getPosition().x;
    float oy = obstacle.getPosition().y;
    float ow = obstacle.getDimensions().x;
    float oh = obstacle.getDimensions().y;
    Texture obstacleTexture;
    switch (obstacle.getWorth()) {
        case 7:  obstacleTexture = redBrickTexture; break;
        case 5:  obstacleTexture = orangeBrickTexture; break;
        case 3:  obstacleTexture = greenBrickTexture; break;
        default: obstacleTexture = yellowBrickTexture; break;
    }
    game.batch.draw(
        obstacleTexture,
        ox, oy, ow, oh
    );
}

Replacing the original obstacleTexture fetch from the asset manager with 4 calls, and then moving the reference down into the loop. Nothing too odd here, besides the fact that I sort of wanted to make a helper to get the obstacle texture, but then realized I'd have to pass the bundle in if I wanted to return a Texture so I dropped the idea.

Look! It's feeling more like breakout already! But also, maybe more importantly, our paddle is way too thick, and our ball is the same color as the bricks, so we can change that, though the color isn't that important since if we use an actual set of assets and not just the colored blocks, we'll be able to tweak all those things then. But I'd like to make the paddle a bit smaller regardless:

Vector2 playerSize = new Vector2(80, 20);

There! That's not bad at all! Let's move on to the last few pieces of the gameplay puzzle!

Walls and movement

So, in case you haven't noticed yet. Our level is unbounded. If the balls bounces off of an obstacle and heads towards the side of the screen or gets past our paddle it just flies away, never to appear again. For a bit of testing I tossed these in while I was working:

// TODO: Make real walls that are seperate from obstacles here
obstacles.add(new Obstacle(new Vector2(-1, -1), new Vector2(1, GAME_HEIGHT)));
obstacles.add(new Obstacle(new Vector2(GAME_WIDTH , -1), new Vector2(1, GAME_HEIGHT)));
obstacles.add(new Obstacle(new Vector2(-1, -1), new Vector2(GAME_WIDTH, 1)));
obstacles.add(new Obstacle(new Vector2(-1, GAME_HEIGHT), new Vector2(GAME_WIDTH, 1)));

But the funny thing was that since I added them to the obstacles list, they get checked for collision and then this line of code makes them single use:

if (toRemove != null) {
    obstacles.remove(toRemove);
}

Which I didn't think about at first and then laughed when the ball did this:

So, we probably shouldn't re-use the obstacle list as a way to track the walls. Now, I think that it was fine as a quick prototyping thing, since I wasn't really trying to bounce things around too much. But as far as checking the walls go, having the full obstacle collision checks on the entire wall just sounds silly. Let's just tell the level how big it is:

private final Vector2 levelSize;
private int score;

public Level(
    Obstacle player, 
    List<Obstacle> obstacles,
    float ballSpeed,
    Vector2 levelSize
) {
    ...
    this.levelSize = levelSize;
}

Then we can just use those dimensions-in-bounds checks during the updates to clamp the user and the ball with a simple helper:

// in both Obstacle and in Ball                    
public void clamp(Vector2 levelSize) {
    position.x = MathUtils.clamp(position.x, 0, levelSize.x - dimensions.x);
    position.y = MathUtils.clamp(position.y, 0, levelSize.y - dimensions.y);
}

And in the level we just call it with our new field in the Level:

public void update(float delta) {
    this.player.update(delta);
    ... collision code and all that ...
    this.ball.clamp(levelSize);
    this.player.clamp(levelSize);
}

Assuming we pass in our game width and height constants (which are 1280 and 800) then the level will keep the ball inside of it. Which is nice.

// in LevelScreen's setup code:                    
Vector2 levelSize = new Vector2(GAME_WIDTH, GAME_HEIGHT);
this.level = new Level(player, obstacles, 50, levelSize);

But this doesn't really bounce the ball off of the walls now does it? So, before we clamp, let's check the position and flip the velocity if we've gone out of bounds along an axis:

public void clamp(Vector2 levelSize) {
    if (position.x < 0 ) velocity.x = velocity.x * -1;
    if (position.x > levelSize.x - dimensions.x) velocity.x = velocity.x * -1;
    if (position.y < 0 ) velocity.y = velocity.y * -1;
    if (position.y > levelSize.y - dimensions.y) velocity.y = velocity.y * -1;
    position.x = MathUtils.clamp(position.x, 0, levelSize.x - dimensions.x);
    position.y = MathUtils.clamp(position.y, 0, levelSize.y - dimensions.y);
}

We're only changing the clamp method like this in the ball by the way. It'd be sort of odd if the paddle bounced when you touched the wall. That said, is there much point in clamping the paddle when we don't have any movement controls yet? 9

I've been using some temporary code while testing that looks like this:

@Override
public void update(float seconds) {
    if (Gdx.input.isKeyPressed(Input.Keys.ENTER)) {
        this.level.getBall().tmpReset().setVelocity(
            new Vector2(MathUtils.random(), MathUtils.random())
        );
    }
    if (Gdx.input.isKeyPressed(Input.Keys.SPACE)) {
        this.level.launchBall(new Vector2(50, 50));
    }
    if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
        this.level.getBall().tmpReset(
            Gdx.input.getX(), Gdx.input.getY()
        ).setVelocity(
            new Vector2(MathUtils.random(), MathUtils.random())
        );
    }
    if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
        this.level.getPlayer().setVelocity(new Vector2(-1, 0));
    } else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
        this.level.getPlayer().setVelocity(new Vector2(1, 0));
    } else {
        this.level.getPlayer().setVelocity(new Vector2(0, 0));
    }
    this.level.update(seconds);
}

The tmpReset function is, as its name suggests, temporary! Maybe it will become a permanent function, but just like the collision and such, I'd like to encapsulate as much as I can into the actual level class. So this leaves us with the question of: how do we want to control the paddle? Also the question of: do we want the ball to launch from the paddle, should the user be able to move around with the ball and then launch? Should the user be able to set the angle at all? Hm…

Those questions are ones we can decide on, but all the temporary code, especially the one setting the ball to the mouse coordinates on left click, is stuff that I'll be throwing out. In LibGDX we have the option to use the global inputs like in the above, or we can localize things with an input handler. I'd like to use one of these for one very simple reason: lower mental overhead.

How many times have you see code like

class SomeClass {
    boolean isFoo;
    boolean isBar;
    boolean isBaz;
    boolean isBoz;

    void someMethodDoingTooMuchAtOnce() {
        if (isFoo) {
            isBoz = someConditionDependingOnThing1
        } else if (isBar) {
            isBaz = someConditionDependingOnThing2
        }
        ... etc
    }
}

And everything works juuuust fine until you accidently mistake isBLA for isBLOO and then some spooky weird behavior starts happening and you're just staring at the code like this and explaining it to your rubber duck of choice like

So! Let's make an input handler for before all the bouncing starts! We'll let the player move the paddle around, move the ball along with it, and maybe even let the user set the launch direction with the mouse or something… Actually, that sounds like too much effort, let's start with the other input handler. The one that just moves the paddle back and forth first.

public class PaddleInputHandler extends InputAdapter {

    private final Obstacle player;

    public PaddleInputHandler(Obstacle player) {
        this.player = player;
    }

    @Override
    public boolean keyDown(int keycode) {
        ...
    }

    @Override
    public boolean keyUp(int keycode) {
        ...
    }

    private void setPlayerVelocityZero() {
        this.player.setVelocity(Vector2.Zero.cpy());
    }

    private void setPlayerVelocityRight() {
        this.player.setVelocity(Vector2.X.cpy());
    }

    private void setPlayerVelocityLeft() {
        this.player.setVelocity(Vector2.X.cpy().scl(-1f));
    }
}

This is just a basic InputAdapter, and I've defined some helper methods that set the obstacle velocity in the same way as the Gdx.input.isKeyPressed code that we had in the update method. In order to get the same behavior though, we need to fill in the key up and key down handlers. The key down handler is super simple, we just need to set the velocity to zero:

@Override
public boolean keyUp(int keycode) {
    switch (keycode) {
        case Input.Keys.LEFT: setPlayerVelocityZero();
        case Input.Keys.RIGHT: setPlayerVelocityZero();
            return true;
        default:
            return super.keyDown(keycode);
    }
}

And more interestingly (but not by much) is the keyDown handler:

@Override
public boolean keyDown(int keycode) {
    switch (keycode) {
        case Input.Keys.LEFT:
            setPlayerVelocityLeft();
            return true;
        case Input.Keys.RIGHT:
            setPlayerVelocityRight();
            return true;
        default:
            setPlayerVelocityZero();
            return super.keyDown(keycode);
    }
}

The key handlers return true if our handler did something with the key and input processing should stop, and false otherwise. Though in our case, the false would be handled by the default super action. This is a classic chain of responsibility pattern, and just like that, we need to make sure that we actually set this chain up! To do that, we need to use the Gdx.input helper to set the input processor which should be actively getting notifications from the library about the changes from the keyboard and other peripherals.

public LevelScreen(GameRunner game) {
    ...
    PaddleInputHandler paddleInputHandler = new PaddleInputHandler(player);
    Gdx.input.setInputProcessor(paddleInputHandler);
}

Once that input processor is registered, we can then remove the old code handling the inputs and our behavior won't have changed at all!

@Override
public void update(float seconds) {
    if (Gdx.input.isKeyPressed(Input.Keys.ENTER)) {
        this.level.getBall().tmpReset().setVelocity(
            new Vector2(MathUtils.random(), MathUtils.random())
        );
    }
    if (Gdx.input.isKeyPressed(Input.Keys.SPACE)) {
        this.level.launchBall(new Vector2(50, 50));
    }
    if (Gdx.input.isButtonPressed(Input.Buttons.LEFT)) {
        this.level.getBall().tmpReset(
            Gdx.input.getX(), Gdx.input.getY()
        ).setVelocity(
            new Vector2(MathUtils.random(), MathUtils.random())
        );
    }
  - if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
  -     this.level.getPlayer().setVelocity(new Vector2(-1, 0));
  - } else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
  -     this.level.getPlayer().setVelocity(new Vector2(1, 0));
  - } else {
  -     this.level.getPlayer().setVelocity(new Vector2(0, 0));
  - }
   this.level.update(seconds);
}

Great! We can move! Now let's do the other input processor, the one which moves the ball for us!

public class LaunchBallInputHandler extends PaddleInputHandler {
    final Ball ball;
    final Vector2 launchVector;
    private boolean isLaunched;

    public LaunchBallInputHandler(Obstacle player, Ball ball) {
        super(player);
        this.ball = ball;
        this.launchVector = new Vector2(1, 1); // TODO!
        this.isLaunched = false;
    }

    @Override
    public boolean keyDown(int keycode) {
        if (keycode == Input.Keys.SPACE) {
            this.isLaunched = true;
            ball.setVelocity(launchVector.cpy().scl(50));
            return true;
        }
        return super.keyDown(keycode);
    }

    public boolean isLaunched() {
        return isLaunched;
    }
}

We're going to add some more to this in a moment, but first let's get that ball tracking behavior that we expect to see at the very beginning of the game. We still want our paddle to move left and right, and it's the exact same code as the other handler, so we can program by difference here and just extends PaddleInputHandler to get that for free! Then, the only thing we care about to get the ball moving around and then launching, is tracking the space bar!

There's not a lot to say here besides I probably shouldn't hardcode the speed for the launch vectory, but it does match what we have in the LevelScreen prototype code. The key thing here is we have a flag, which the outside world can see via isLaunched and that tells the rest of the code if we need the ball to be glued to the paddle or not like this:

You might ask yourself: why is there no code in the input handler to keep the ball glued? And that's because the input handler is only called when there's input (crazy, I know)! Therefore, if we were to do something like this:

if (!isLaunched)) {
    this.level.getBall().centeredOnTopOf(this.level.getPlayer());
}                    

Then it would ever move once:

So that's no good! Instead, let's follow what talked about earlier, in keeping the state of the game in the Level object! We'll tell the level when it should be glueing that ball to the player's center position or if it should be launching things out. We can do this with a simple boolean flag:

public class Level {
    ...
    private boolean ballLaunched;

    public Level(
        Obstacle player,
        List<Obstacle> obstacles,
        float ballSpeed,
        Vector2 levelSize
    ) {
        ...
        this.ballLaunched = false;
    }

    public void launchBall(Vector2 velocity) {
        this.ballLaunched = true;
        this.ball.setVelocity(velocity);
    }

    public void update(float delta) {
        this.player.update(delta);
        ... collision detection code ... 
        if (!ballLaunched) {
            this.ball.centeredOnTopOf(this.player);
        }
        ...
    }

And of course, we'll need to add a new method to the ball:

public void centeredOnTopOf(Obstacle obstacle) {
    Vector2 center = obstacle.getCenter();
    this.position.x = center.x;
    this.position.y = obstacle.getPosition().y + obstacle.getDimensions().y + 1; 10
}

Then we have the expected behavior that I showed before. This is useful, but there's still one more thing we want the fancy LaunchBallInputHandler to do for us, which is set the launch vector! But before we do that, we should fix a bug! Right now, if you hold the left key and then the right key, and release either of them the paddle stops abruptly.

If you look at the PaddleInputHandler you can probably see why this sequence of key presses would do this. We need to not call setPlayerVelocityZero unless the player isn't pressing any buttons down. You might initially think: "Oh, just add a boolean in and tweak it each time", but that won't work either because unless you have a boolean for each key you're tracking, it will just get reset in the same way that causes the bug now.

So instead, we can just keep track of the number of keys pressed, and increment, decrement, and compare as needed to determine if we should zero out the velocity of the paddle and ball or not. Like so:

private int numberOfKeysPressed;

public PaddleInputHandler(Obstacle player) {
    this.player = player;
    this.numberOfKeysPressed = 0;
}

public boolean keyDown(int keycode) {
    switch (keycode) {
        case Input.Keys.LEFT:
            this.numberOfKeysPressed++;
            setPlayerVelocityLeft();
            return true;
        case Input.Keys.RIGHT:
            this.numberOfKeysPressed++;
            setPlayerVelocityRight();
            return true;
        default:
            return super.keyDown(keycode);
    }
}

@Override
public boolean keyUp(int keycode) {
    switch (keycode) {
        case Input.Keys.LEFT:
        case Input.Keys.RIGHT:
            this.numberOfKeysPressed--;
            if (this.numberOfKeysPressed == 0) setPlayerVelocityZero();
            return true;
        default:
            return super.keyDown(keycode);
    }
}

And with that, we've got smooth back and forth paddle gliding. But what we don't have is a way to set the launch vector. That's not too bad now that we've got an input handler to tweak and adjust:

public class LaunchBallInputHandler extends PaddleInputHandler {
    final Vector2 launchVector;

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        Vector2 center = this.level.getPlayer().getCenter();
        float width = this.level.getPlayer().getDimensions().x;
        float clampedMx = MathUtils.clamp(
            screenX, center.x - width/2f, 
            center.x + width/2f
        ) - center.x;
        float unitX = clampedMx / width;
        launchVector.x = unitX;
        launchVector.y = 1;
        return super.mouseMoved(screenX, screenY);
    }

We're using the position of the mouse (which is in raw absolute pixel positions, not in game units) to see if the mouse is to the left or right of the player paddle. Technically speaking, I should unproject these screen coordinates to align things. But, I only really care about the relative position of the mouse so for now I'm going to skip it until it becomes a problem.

The math here isn't really anything too crazy. As the name suggests unitX is normalized based on the centerpoint of the paddle. So if you move your mouse to the left of the paddle, you get -0.5, and if you move it to the right, you get 0.5. Not 1 since we're treating the centerpoint of the paddle as 0 and the full length of the clamped value is just 1 widths worth of paddle.

I thought about if I should put this into the Level itself, but the call to level.launchBall when we press space in this handler works just fine I think. That said, it'd be good if we can visualize this in some way for the user so they understand. We probably want an arrow or something like that once we get some assets in place, so we'll need to deal with rotation of an asset then, but for now, we can simply show a little block to make things simple. So in the render method of LevelScreen we can go ahead and add this:

float px = level.getPlayer().getPosition().x;
float py = level.getPlayer().getPosition().y;
float pw = level.getPlayer().getDimensions().x;
float ph = level.getPlayer().getDimensions().y;
...
if (!launchBallInputHandler.isLaunched()) {
    Vector2 launchVector = launchBallInputHandler.getLaunchVector();
    float lvx = MathUtils.lerp(px, px + pw, launchVector.x + 0.5f);
    game.batch.draw(
        playerTexture,
        lvx,
        py + ph + 15, // y offset from the paddle by a bit
        10, 10        // width and height
    );
}

The vector from -0.5 to 0.5 needs to be converted back into an offset to take from the paddle position, so we + 0.5f to the x value in order to determine the value between 0 and 1 to count as the progress for the linear interpolation method between the left and right edges of the paddle.

Perfect! So now we can launch the ball off at the angle we want to. We definitely still need to go about tweaking that hardcoded speed value, but I think we need to address the fact that the game doesn't really have an end yet.

Scoring and game end

We already added the actual variable to track the score, and even added the brick worth. But we're not displaying it to the user at all yet. To do that we need to load a font up and display it to the user, and we need to figure out where we'll even put it on the screen. The original break out put it at the top, though the wikipedia page also notes:

Near the end of development, Wozniak considered moving the high score to the screen's top, but Jobs claimed Bushnell wanted it at the bottom; Wozniak was unaware of any truth to his claims.

The screenshot in the wikipedia page shows the score at the top, but we're kind of running out of room and things feel a bit claustrophibic, so I think we need to squish the padding a bit more. Then we can put the score at the top I guess. 11 I'll try to give myself 50 units of space and then reduce that padding like so:

Vector2 brickSize = new Vector2(150, 30);
float padding = 15 + brickSize.y ;
float offset = 50;
for (float x = 40; x < GAME_WIDTH - 50; x += brickSize.x + 25) {
    obstacles.add(Obstacle.RedBrick(new Vector2(x, 800 - padding - offset), brickSize));
    obstacles.add(Obstacle.RedBrick(new Vector2(x, 760 - padding - offset), brickSize));
    obstacles.add(Obstacle.OrangeBrick(new Vector2(x, 720 - padding - offset), brickSize));
    obstacles.add(Obstacle.OrangeBrick(new Vector2(x, 680 - padding - offset), brickSize));
    obstacles.add(Obstacle.GreenBrick(new Vector2(x, 640 - padding - offset), brickSize));
    obstacles.add(Obstacle.GreenBrick(new Vector2(x, 600 - padding - offset), brickSize));
    obstacles.add(Obstacle.YellowBrick(new Vector2(x, 560 - padding - offset), brickSize));
    obstacles.add(Obstacle.YellowBrick(new Vector2(x, 520 - padding - offset), brickSize));
}

And this looks like this:

Now that we've got space for it, let's add in the score and watch it go up as we break the obstacles. To load a font other than the default one, we need to set up our asset loader to deal with true type fonts. If I were to use code adapted from the previous two games we've made, I could do something like this:

public static final String SCORE_FONT_KEY = "visitor50.ttf";
public static final AssetDescriptor<BitmapFont> scoreFont = visitorFontDescriptor(50, SCORE_FONT_KEY);

public static AssetDescriptor<BitmapFont> visitorFontDescriptor(int sizeInPixels, String key) {
    FreetypeFontLoader.FreeTypeFontLoaderParameter params = new FreetypeFontLoader.FreeTypeFontLoaderParameter();
    params.fontFileName = "visitor/visitor1.ttf";
    params.fontParameters.size = sizeInPixels;
    return new AssetDescriptor<>(key, BitmapFont.class, params);
}

static public BitmapFont getScaledFont(AssetDescriptor<BitmapFont> key, AssetManager assetManager) {
    BitmapFont font = assetManager.get(key);
    font.setUseIntegerPositions(false);
    font.getData().setScale(
        (float) GAME_WIDTH / Gdx.graphics.getWidth(),
        (float) GAME_HEIGHT / Gdx.graphics.getHeight()
    );
    return font;
}

This should work, but I can't help but think about the fact that we created a custom loader for the scene and temporary blocks. If we made an extra loader for these true type fonts then we could ditch the static method to scale the fonts by the height and width of the game screen. The only issue with that being that I suppose we'd need to re-load the font from that delegate loader if the screen is resized in order to scale properly again.

So, we have to ask ourselves: is this something that will be useful in a future game that we can re-use? Or should we just use the existing code and move along? I think it would be interesting, but also, I think we should just get the dang score onto the screen. So, on one hand we can re-use this code from the other projects verbatim to move along quickly. On the other hand it's awkward to fetch the font through the static helper when we've been using the bundle.get helper for all the textures. But on the gripping hand, we only need to initialize fonts when we start and if we resize the screen, so we can just lift the bitmap font to the same level as the sprite batch and let it be special.

// In GameRunner
public BitmapFont font;
...

@Override
public void create() {
    assetManager = new AssetManager();
    GameAssets.configure(assetManager);
    assetManager.load(GameAssets.scoreFont);
    assetManager.finishLoading();
    font = GameAssets.getScaledFont(GameAssets.scoreFont, assetManager);
    ...
}

And then we can access it as needed from the level screen to draw the score above the obstacles. Though we do have one problem. Observe, the red text:

game.font.draw(
    game.batch,
    "Score: " + level.getScore(),
    0, 780,
    GAME_WIDTH, Align.center, false
);

I declared the score, we incremented it by the worth of the obstacles being destroyed, and we never declared a getter. Still, that's easy because one Alt + Enter in Intelliji later and we've got the getter in place. Easy. I do love code snippets, they help me blog, they help me save myself from carpal tunnel over time…

And they get us a setter and a score displayed:

Perfect, so now the player can have fun breaking all the balls but uhm. Well, two problems:

Maybe three problems if you consider how slow the ball moves. But the main two I want to highlight is that we can break all the obstacles and nothing happens. And that the ball is currently allowed to bounce off the bottom of the screen with no consequence.

Both are these have the same solution! We need our Level class to detect these cases and emit an event to let the rest of the game know about that. At the moment, the level is supplying a getter for if the ball was launched or not, but that won't really work for this type of thing since we clamp the ball down within the update method. If the ball's clamped, then anyone peering in to observe the state of the game won't ever see it past the boundary.

So instead, we need to make our Level a publisher of events via the usual Observer pattern we've used in the past. Let's define the interface for the consumers of the events first:

public interface LevelEventConsumer {
    public void onBallOutOfBounds(Level level);
    public void onObstacleRemove(Level level, Obstacle obstacle);
    public void onAllObstaclesRemoved(Level level);
}

Then we can have the level keep track of a list of consumers who will be notified when such things happen. This will let the level code stay pure Java with no extra engine or library stuff, and we can hook in things like sound or effects in the more UI focused code with that as needed.

public class Level {
    private final List<LevelEventConsumer> consumers;
    public Level(..) {
        ...
        this.consumers = new LinkedList<>();
    }

    public void addConsumer(LevelEventConsumer levelEventConsumer) {
        this.consumers.add(levelEventConsumer);
    }

    ...

All these events can fire from within the update method. Two of which can fire when we've detected that there's an obstacle that's been hit and we should remove it:

if (toRemove != null) {
    for (LevelEventConsumer consumer : consumers) {
        consumer.onObstacleRemove(this, toRemove);
    }
    obstacles.remove(toRemove);
    if (obstacles.isEmpty()) {
        for (LevelEventConsumer consumer : consumers) {
            consumer.onAllObstaclesRemoved(this);
        }
    }
}

The onBallOutofBounds can launch at the end of the method right before we run the update:

    ...                    
    if (this.ball.getPosition().y <= 0) {
        for (LevelEventConsumer consumer : consumers) {
            consumer.onBallOutOfBounds(this);
        }
    }
    this.ball.clamp(levelSize);
    this.player.clamp(levelSize);
}

Though, none of this does us any good if no one is listening of course. So, let's update the level screen that's managing things and have it do some work for us. I don't know if I think that the level screen should keep track of how many lives we have total 12, but for now, let's go ahead and add in a couple useful class level variables:

private final LinkedList<Object> obstaclesToDisappear;
private int lives;
private boolean gameOver;

Then our consumer just tweaks those as you'd suspect:

public LevelScreen(GameRunner game) {
    ...
    lives = 3;
    obstaclesToDisappear = new LinkedList<>();
    this.level = new Level(player, obstacles, 50, levelSize);
    this.level.addConsumer(new LevelEventConsumer() {
        @Override
        public void onBallOutOfBounds(Level level) {
            lives--;
        }

        @Override
        public void onObstacleRemove(Level level, Obstacle obstacle) {
            // We could play a sound here or similar,
            // but for now we'll just add them to a
            // list we'll fade out or similar.
            obstaclesToDisappear.add(obstacle);
        }

        @Override
        public void onAllObstaclesRemoved(Level level) {
            // go to the win screen and what have you. For now though...
            gameOver = true;
        }
    });

I think the boolean for gameOver will disappear after we add code to change screens, but for now that gives us enough to do something with, which is better than nothing. So first up, let's show the number of lives left on the screen!

public void render(float delta) {
    ...
    game.font.draw(
        game.batch,
        "Balls: " + lives,
        0, 780,
        GAME_WIDTH / 3f, Align.center, false
    );
    ...

Easy enough, though it's a bit awkward that the ball just keeps going huh? Maybe we should get a little helper function to reset the ball and what not so that the player can line their shot up. We'll need to reset the ball in the Level class:

public void resetBall() {
    this.ballLaunched = false;
    this.ball.setVelocity(Vector2.Zero.cpy());
    this.ball.setPosition(
        this.player.getPosition().add(
            player.getDimensions().x/2f,
            player.getDimensions().y
        )
    );
}

and then in the LaunchBallInputHandler reset the boolean we're using to track the launch:

public void resetLaunched() {
    isLaunched = false;
}

And lastly, hook it all together when the ball goes out of bounds:

this.level.addConsumer(new LevelEventConsumer() {
    @Override
    public void onBallOutOfBounds(Level level) {
        lives--;
        launchBallInputHandler.resetLaunched();
        level.resetBall();
    }
    ...

Easy.

So that's one event handled. How about those obstacles that we're removing? The reason I'm pulling them over into a seperate list is that if we were to play some kind of animation of the brick breaking (once we have 'real' assets), then this is where we'd want to drop those in. Since we just have colored blocks, I think I'll just tweak it to reduce the size of the obstacle itself down to nothing. Not as fun as an explosion, but a bit more fun than the block just disappearing on the next frame.

To faciliate this, we need a new helper class:

import java.util.function.Supplier;

public class TimedAction {
    private float accum;
    private final Supplier<Boolean> action;
    private float period;
    private boolean done;

    public TimedAction(float period, Supplier<Boolean> action) {
        this.period = period;
        this.accum = 0;
        this.action = action;
        this.done = false;
    }

    public void update(float secondsSince) {
        accum += secondsSince;
        if (accum > period) {
            accum -= period;
            done = action.get();
        }
    }

    public boolean isDone() {
        return done;
    }
}

I suppose maybe I should have called this periodic action or something, but whatever. The goal of the class is to tick up the accumulated seconds and whenever we pass the threshold, call the action we supply it with. The done value will cycle between true and false depending on if the last update call crossed a threshold or not. But in our use case of it, we'll only ever end setting the value to done when we're good and ready.

Adding in another class field into the level screen

private final LinkedList<TimedAction> removeActions;

We can track one timed action per obstacle that has to be removed over time:

@Override
public void onObstacleRemove(Level level, Obstacle obstacle) {
    // We could play a sound here or similar,
    // but for now we'll just add them to a list we'll fade out or similar.
    obstaclesToDisappear.add(obstacle);
    removeActions.add(new TimedAction(0.100f, () -> {
            float y = obstacle.getDimensions().y - 1;
            obstacle.setDimensions(obstacle.getDimensions().x, y);
            if (y <= 0) {
                obstaclesToDisappear.remove(obstacle);
                return true;
            }
            return false;
    }));
}

And by "removed over time", as you can see, I mean the dimensions shift towards 0 units tall. I'll omit the code, but know that I updated the render code to loop over the obstaclesToDisappear and render them just like the other obstacle loop. The combination of our new timed action and supplier results in this effect:

Not winning any awards with this one. But I think it's fine for now, so we'll keep that and move along. We've got one more event to handle after all. When we run out of obstacles, it's time to end the game. We've setup that gameOver boolean earlier, but we're not doing anything with it yet.

So, what do we do? Well. I'm a little tired 13 so let's just do the bare minimum here. We'll set up a new scene that will render the congratulatory text:

public class WinScreen implements Scene {

    private final GameRunner game;

    public WinScreen(GameRunner game) {
        this.game = game;
    }

    @Override
    public void render(float delta) {
        ScreenUtils.clear(Color.BLACK);

        game.font.draw(
            game.batch,
            "You win!",
            0, GAME_HEIGHT / 2f,
            GAME_WIDTH, Align.center, false
        );

    }

    @Override
    public void update(float seconds) {
        if (Gdx.input.isKeyPressed(Input.Keys.ESCAPE})) {
            Gdx.app.exit();
        }
    }

    @Override
    public String getBundleName() {
        // Since we're not setting up an actual bundle, this
        // doesn't really matter
        return GameAssets.LEVELSCREEN_BUNDLE_KEY;
    }
}

And then, we can update the GameRunner to swap to let us change what the current scene is. Super simple:

public void swapSceneTo(Scene scene) {
    this.currentScene = scene;
}

And lastly, the event handler inside of LevelScreen just calls this with a new instance of the scene.

@Override
public void onAllObstaclesRemoved(Level level) {
    game.swapSceneTo(new WinScreen(game));
}

Nice. So, while EXTREMELY basic, and in no way going to make any proper gamer happy. For a prototype, it works. And if it fits, it sits, as it were.

Speeding up the ball

Now, like I said, I kind of want to move onto the next game since this one didn't really add too much that was different from pong. But it wouldn't be breakout if we left it like it is right now. It'd be more… boreout. I mean, have you seen how slow that ball moves in the little video snippets so far? Looking at the requirements listed on the challenge page we have a fix though:

The ball’s speed should increase as bricks are broken.

I think this will go a long way in making this more fun. It's not fun to s l o w l y   w a t c h   t h e    b a l l   m o v e. So let's refresh ourselves on how the speed is set now, and then figure out how we want to tweak it.

public Level(
    Obstacle player,
    List<Obstacle> obstacles,
    float ballSpeed,
    Vector2 levelSize
) {
    ...
    this.ballSpeed = ballSpeed;
    ...
}

We pass the ball speed along in the level (and set it to start as 50 via LevelScreen's setup code) and then reference it when we launch the ball:

public class LaunchBallInputHandler extends PaddleInputHandler {
    ...
    @Override
    public boolean keyDown(int keycode) {
        if (keycode == Input.Keys.SPACE) {
            this.isLaunched = true;
            level.launchBall(launchVector.cpy().scl(level.getBallSpeed()));
            return true;
        }
        return super.keyDown(keycode);
    }

So, that's good. But, how should we increase the speed of the ball? We don't want to always have it going a mile a minute or else it will just be too fast, so when we launch again after a missed hit, we'll reset back to our regular ball speed. But, when we hit an obstacle, we can tweak the code there with one little method call. In the Level code:

private void collideBallWithObstacle(Obstacle obstacle, float delta) {
    Vector2 ov = obstacle.getVelocity();
    Vector2 reflection = ball.getCenter().sub(obstacle.getCenter());
    reflection.x = Math.signum(reflection.x);
    reflection.y = Math.signum(reflection.y);
    Vector2 bv = this.ball.getVelocity();
    bv.x = Math.abs(bv.x);
    bv.y = Math.abs(bv.y);
    Vector2 v = bv.scl(reflection).add(ov).scl(1.1f);
    this.ball.setVelocity(v);
    this.ball.update(delta);
}

As a wild guess, I figured 10% increase would be pretty nice. And it's not terrible, though there is one minor problem:

Eventually the ball is moving a lot faster than the paddle, so you can't hit it anymore. In the above case, I probably could have still grabbed the ball, but eventually it would definitely be too fast. So we need to put a max on the scaling. I logged out the values as I bounced the ball back and forth until it hit the point where I couldn't catch it anymore.

[V] (0.0,0.0)
[V] (13.977432,55.0)
[V] (-15.375175,-60.5)
[V] (16.912693,66.55)
[V] (-18.603962,-73.205)
[V] (20.46436,80.525505)
[V] (-22.510796,-88.57806)
[V] (24.761875,97.43587)
[V] (-27.238064,-107.17946)
[V] (-29.96187,117.89741)
[V] (32.958057,-129.68715)
[V] (-36.253864,142.65587)
[V] (39.879253,-156.92146)
[V] (-43.86718,172.61362)
[V] (48.2539,-189.87498)
[V] (-53.07929,208.86249)
[V] (58.38722,-229.74875)
[V] (-64.225945,252.72363)
[V] (70.648544,-277.996)
[V] (-77.7134,305.79562)
[V] (85.48474,-336.37518)
[V] (-94.03322,370.0127)
[V] (-103.43654,-407.01398)

Though, I think right around 250 was where the ball started going fast enough that I felt like it would be challenging, but not too bad most of the time if there was enough vertical space for the ball to bounce. Human brains handle anticipation pretty well after all, so we can generally tell, if the ball bounce is predictable, where we should get the paddle to even before we hit it.

So let's max the velocity out at 250 then. Thankfully, the vector class has a clamp function for us to use already which is nice.

Vector2 v = bv.scl(reflection).add(ov).scl(1.1f);
v.clamp(-250f, 250f); // clamps to min - max

That handles our max speed problem. Though, we still have one other little thing to take care of that I forgot in the last section.

You uh, notice anything odd about this?

We display how many balls are left. We decrement them each time we hit the bottom of the level. But we never dealt with what to do when they're actually all gone! This shouldn't be too difficult though, we can basically clone our win screen and make it into a lose screen instead! The only difference in the screen would be:

@Override
public void render(float delta) {
    ScreenUtils.clear(Color.BLACK);
    game.font.draw(
        game.batch,
        "You lose!",
        0, GAME_HEIGHT / 2f,
        GAME_WIDTH, Align.center, false
    );
}

Then we just have to swap to it when the event decrementing the lives fires:

this.level.addConsumer(new LevelEventConsumer() {
    @Override
    public void onBallOutOfBounds(Level level) {
        lives--;
        launchBallInputHandler.resetLaunched();
        level.resetBall();
        if (lives <= 0) {
            game.swapSceneTo(new LoseScreen(game));
        }
    }
    ...

Not hard at all. Mainly because we're being lazy about these things. But hey, something is better than nothing as far as getting the basic ideas out the door and learning a few things here or there.

A title screen

Part of me wants to just say "I want to go make space invaders now", the other part of me is much much more stubborn. So, before we call this game done, let's add a title screen that will show the name of the game, give a little bit of direction, and let the player not get jump scared into trying to figure out how to play the moment they boot up the game. Shall we?

Just like the previous Win and Lose screen, we can set up some really simple code for this. If I content myself to just use text, than we can display the title, instructions, and a couple options pretty quickly. If I want to use one of the assets, we could use the "ball" as the option selector, similar to what we did in the pong game.

For now though, the title screen will do the same thing as the other couple of screens we've made so far. Taking the game in as its only parameter and telling the asset manager to load the resource it needs in the constructor:

public class TitleScreen implements Scene {

    private final GameRunner game;

    public TitleScreen(GameRunner game) {
        this.game = game;
        game.assetManager.load(GameAssets.LEVEL_SCREEN_BUNDLE);
    }
    ...

Then, to show the text and instructions we can make a simple render method. There's really nothing of note to say here, the y positions I'm choosing arbitrarily and we'll see if they look okay or not. My guess is we'll need to adjust it, but let's get something onto the screen first:

@Override
public void render(float delta) {
    ScreenUtils.clear(Color.BLACK);

    game.font.draw(
        game.batch,
        "Bust Out!",
        0, GAME_HEIGHT - 100f,
        GAME_WIDTH, Align.center, false
    );

    game.font.draw(
        game.batch,
        "Arrow Left and Right to move paddle\nSpace to launch ball!",
        0, GAME_HEIGHT - 400f,
        GAME_WIDTH, Align.center, false
    );

    game.font.draw(
        game.batch,
        "Press Enter to start",
        0, GAME_HEIGHT - 600f,
        GAME_WIDTH, Align.center, false
    );

}

and making the instructions real by having the update method handle checking the enter key for if we should swap to the level or not:

@Override
public void update(float seconds) {
    if (Gdx.input.isKeyPressed(Input.Keys.ENTER)) {
        game.swapSceneTo(new LevelScreen(game));
    }
}

Nothing new here. But now, the game doesn't just drop you into the level screen. So a gamer will know what to do were they to play this game. Behold, our "beautiful" title screen in all it's random Y position values chosen glory:

It's not the worst prototype title screen we've done before. Definitely would fit in on an atari machine or something similar I guess. Or at least, it makes me think about my local barcade. So it's got the right feeling. More importantly, it functions as a title screen. So, checkmark done for the game's requirements that I've imposed on myself. I think I can stop being stubborn now, so let's wrap things up and make some notes for the future.

Wrapping up

Welp. We have a game that works, but obviously has plenty of room for improvement. The problem though is that I don't really feel like improving it! Proof-reading this post a bit, at the start of things I was filled with a lot of wistfulness for what I could do with breakout. The types of things to mix in like power ups, paddle size changes, loading differently shaped levels from a file, and all sorts of things like that.

I still think all those would be pretty neat to do.

But, the trouble is that life gets in the way sometimes and a happy mood you had one moment can disappear in the next. Then things compound on each other until motivation gets squeezed out and you're left with a half baked implementation and a lot of ideas. It's important when this happens, and it will happen, to have some way of pushing through and building up momentum again.

In my case, writing these posts help me do that. Even if it's just a couple paragraphs and a little bit of code a day, if I get even a little bit committed to the git repo, than I'm making progress. Needing to proof-read can also hype me up to get back at it too. Reading about some ideas you had, reading through and building momentum of where your ideas were going, retracing your thought-steps to the logical conclusion, all of that can squeeze out more progress even when the mood dips starts to wane.

Playing the game itself can be motivating too. Slow ball speed isn't fun? Try playing it, try being the user for a bit. Seeing how bad it feels to play can either make you feel bad, or it can motivate you to make it better. After all, the fun of a solo dev experience is that you can make it better, you just have to figure out how.

The things that were killing my motivation here were obvious I think. The math for obstacles was really frustrating. Simple box collision worked before, and it worked for the match 3 game, and then for some reason a glitch that feels simple becomes a whole "counter clock wise" math segment thing. Unit tests not passing, and just getting the feeling that I've become dumber as I've aged is just disheartening. Combine that with the fact that I'd prefer for these blog posts to be fun and entertaining, and I've got a recipe for disappointment in myself on multiple fronts as I let my impatience seep into the ink.

Why don't you just rewrite the post then? Why not come back later? is what you might ask. As noted, I'm stubborn. And I'm stubborn because I know that the abandoned programming projects can pile up, and have a momentum of their own. Also, I think it should be obvious, but these little dev logs are intended to show the whole process. Not just the happy path, but the difficulties too.

Not everyone can be good at everything, but everyone can push through problems if you try hard enough. You can reach out for help, get hints from people, and sometimes discover that since you pushed a bit further than you thought you could, you ended up further than expected. I learned a cool trick for understanding line segment overlap! Granted, it felt inefficient, but there's always a trade off between realism and performance when it comes to physics simulations, so that's hardly a surprise.

Anyway. I think that we could greatly improve the game. Load in real assets, add sound effects, add powerups and powerdowns. Change up the level structure, have multiple levels in an arcade story-mode of some sort. Tons of stuff you can do with breakout! But for now, that's not going to be what I'm going to work on. This game was far too similar to pong, and had all the downsides of what that entails without any new interesting challenge to face beyond my own impatient and insecurity. So, let's move onto the next game. Those improvements I mentioned? Consider them an exercise for the reader. You've got the rough prototype, I believe in you. You can make something awesome if you apply yourself.