I've been using Leaflet now for about a year, watching grow from "write pages on the internet" to "fledgling publishing tool competitor." Having a minimalist tool to just write stuff in without getting all up in my head about the bigger picture (TM) is a real blessing; I can post in a flurry of content or I can chill out/get distracted by other things and not post for months. It's a project I am pleased to support, both via pull requests (got one for picture captions in the works...) and through Leaflet Pro (which also means the three friends who don't have a Bluesky/Atmosphere account can get my posts via email).

Things plan to be quite hectic for another month or two with a move and some unexpected travel (not to mention total chaos instigated by a gas leak, zombie dishwasher, and a leaking toilet1). Still, I've got a bit of momentum, so it seems like a good idea to consolidate my writing over here. Leaflet is part of the Atmosphere, meaning if/when they sell out I can easily ditch them since the articles are hosted on a computer (PDS) I own2. I'm going to maintain my personal website for the foreseeable future, but you can subscribe to these posts from there as well thanks to the nice little embedded widget that comes with Leaflet Pro.

Screenshot of grahamewatt.com showing the leaflet embed widget.

To start with, here is a double feature about building a clone of the classic flash game Curveball in Unity. You can play it at grahamewatt.com/games/corvebawl.


Remaking Curveball

Originally published as "Remaking Curveball"

Only 90's kids will remember this

For those of us who grew up in the 90's, surfing the internet was like traveling to California during the gold rush. DSL and Broadband was starting to leak out into the consumer market, freeing us from the tyranny of dial-up modems and metered connections. Consolidation had yet to happen, despite the best efforts of Yahoo, AOL, and MySpace; the diversity of websites was BANANAS. People had bookmark lists in the hundreds; ebaumsworld was a thing3, and there was one glorious new technology that had captured the hearts and minds of kids and teens around the world:

Flash games.

Powered by Macromedia (now Adobe) Flash, the internet was awash in games and cartoons of all shapes and sizes. Some were funny (Decline of Video Games), some were chaos (Boxhead), some were just weird (Homestar Runner), and some?

Some were awesome.

Screenshot of the game Curveball

A classic

Curveball was one of those games. Not much more than 3D Pong, it somehow managed to become one of the most played flash games of the late 90s. Not that Pong wasn't addictive in its time, but Curveball managed to shine in a world that was not just discovering video games for the first time.4 It's anyone's guess as to why, but if I had to guess, I'd say it was the spin mechanic: by moving the paddle as the ball made contact with it, you could apply topspin to the ball, causing it to hook and bend like crazy. It felt realistic, it felt controllable, and it was SO MUCH FUN.

Unfortunately, Curveball relies on Flash. Flash is insecure for a number of reasons, and most of the major browsers are in the process of phasing out support for the technology.5 So playing it requires going back to Internet Explorer. Which I'm not willing to do.

So I decided to build it myself. Over the last year or so, I've been teaching myself to program and have been playing around with Unity, one of the two major game engines on the market today. Building a clone meant I would have a finished game under my belt, which is a milestone I had yet to reach. It was a fun process, challenging at times, but I build a complete v1 game that used most of the main systems in Unity:

  • Physics Engine

  • Input Manager

  • Shaders/Textures

  • Post-processing effects

  • Audio

  • UI

The result? Corvebawl:

Corveball, a blocky version of Curveball made by the author.

I’m hosting it here, so please check it out and let me know what you like/dislike about it. If you’re interested in playing around with the project yourself, I’ve uploaded the unitypackage file for you to download.6

I have a full post-mortem you can check out if you’re interested in some of the lessons learned and challenges I faced in actually building the game.


Curveball Post-mortem

Originally published as "Curveball Post-mortem"

I spent a couple of days rebuilding the classic flash game Curveball in Unity. You can play the game in the Games section of my website. As always, I put together the following collection of thoughts lessons learned from the development process.

Architecture

Corvebawl is a simple game and has a simple code base as a result. A Game manager controls the game state, a UI manager controls the UI (menus, volume, score), the players each extend a Paddle class with the PlayerPaddle containing input logic and the PaddleAI containing AI logic. The ball itself contains the most complicated series of scripting: it has several functions used to handle how it behaves when hit by each paddle, how spin and curve is calculated, and path tracing used by the AI paddle to predict where it will end up. A few other special effects scripts round out the code base.

None of this required anything particularly complex to implement. In fact, my biggest mistake in programming was likely overbuilding things to start with. I had barely set up the game arena in the Unity editor before I was creating classes for every single object in the scene. I did manage to dial a chunk of it back, but my code is still somewhat overbuilt for what it needed to be. I wrote several classes that I ultimately ended up deleting, as they had no real purpose.

Lesson: Don't build things until you need them. Keep it simple and clean.

AI Implementation

Perhaps the biggest challenge I had in programming was creating a competent AI. Initially, my plan was to implement a solution I had found on Stack Exchange:

Invisible Ball AI
AI Setup: When the ball reflects off your paddle, you know where it is and how fast it is going. Spawn an invisible ball at that point but at a greater speed. It will wind up where the visible ball is going. Each frame, have the AI move towards the location of the invisible ball. Stop the invisible ball once it reaches the AI's side, so it is where the AI should move its paddle.
Results: The AI looks like it's trying to predict the path of the ball. Say the player has reflected the ball at a steep angle so that it will bounce off a wall. The AI will track the ball down a little ways, and then—being slower than the ball—will fail to track it back up fast enough. You have tricked the AI, and it looks fairly logical from a human point of view. You can see the computer trying to predict where the ball will go, and then it moves—oh, it missed, it was too slow, and you have won a point.
This is significantly better than inserting randomness, since it makes the AI look relatively intelligent. A worthy opponent. It also lets the AI play by the exact same rules as the human, which looks better to the player and makes your job easier.

It took me a little bit, but I implemented this without too much difficulty... and it didn't work. The AI was good for a point or two, but then would seem to completely fly off the rails. What was going on?

The solution I was attempting to use is great for 2D pong. It is, in effect, a form of raytracing with a ray that isn't instantaneous. It is not great for pong with spin and curve, since increasing the speed of the ball increases the distance it moves in each frame, which requires adjusting all of the other variables in the curve, which means essentially creating a new position function on the fly that matches the standard function.

It was a mess, and so after I was unable to wrap my head around the required multivariable calculus7, I implemented the current solution. The AI now ignores spin entirely, and draws a path from the ball to the back wall, bouncing it off the sides as appropriate. It then moves towards the XY coordinates of that spot. The AI does not have perfect predictive power, but it's actually a good thing: it feels flawed, which is appealing to the player.

To improve the realism, I limited the AI to only be able to count a maximum of two bounces, which causes it to go the wrong way as the ball ricochets around the arena. Looks very human, and provides a solid challenge.

Lesson: In game dev, "good-enough" simple solutions are often better than complicated "perfect" ones

Input Management

Normally, input is not an issue for me. It was one of the first things I learned how to do, and I'm comfortable using the Unity Input Manager to control keybindings, and even allow the user to program their own bindings. For a game like Corvebawl, where it was only ever going to go on the internet, I could have even hard-coded my input controls.

But then the shiny new Input System caught my eye, and I thought I would make the switch. After all, newer is better right?

Wrong. The Input System is definitely more advanced, and I can see where the flexibility would be an advantage. Unfortunately, it meant learning a whole new system, one that is by all accounts not quite finished. I spent a lot more time than was necessary trying to figure out how to make the new system do what I wanted it to do, things that normally would have taken a few seconds for me to code.

Lesson: Stick with what you know. Newer is not always better.

Project Scope

I think the biggest challenge in any creative exercise is setting boundaries and a defined target and then staying on the path. Doing so flies in the face of our instincts and requires delaying gratification. It feels so satisfying in the moment to imagine complicated structures, elegant flows, worlds that seem so perfect in our head, and it sucks when imagination hits the brutal reality of the real world.

I had similar issues sticking to scope with this game. Even a game as simple as pong gets the juices flowing. I wanted first to extend it into a VR tennis-type game, then I wanted to have power-ups and spin options, then I wanted to implement a zillion game modes... all of which I still can do in the future. But limiting my scope to one game mode (endless pong w/ difficulty settings) allowed me to focus, and wrestle with the challenges I was avoiding in UI and shader development.

It's the question of making the build process feel a bit more like work and less like fun in exchange for having a final product you can be proud of.

Lesson: Set a scope, ideally up front, and don't be distracted by all the crazy ideas the build process generates—they'll still be there for the next one.