Last time, we took a look at creating a web version of Pong, the classic Atari game. Here is an animation of what we ended up with:
Pretty cool! It's a fun game to play, even after all these years. And it was even more fun figuring out all the little things that made Pong so compelling.
In this post, I'll share some of the steps I took to adapt the Pong implementation into Breakout.
The original goal for these blog posts was to share step-by-step tutorials of how to recreate classic games. But the intent has evolved into sharing some interesting notes, ideas, and takeaways about the process of building these games.
⏳ tl;dr
In the last blog post, I buried the links to the playable demo and the source code all the way at the bottom of the post. It was my devious intention to force everyone to take hours of their precious time to scroll through my shoddy writing. This time, I'll link to them here in case you want to try the game out before reading on.
🛸 Why Breakout?
After working on Pong for a while, I was dying to move on to a different game genre. I've always wanted to learn how to create platform games like Super Mario Bros. and adventure games like The Legend of Zelda. But I decided to try Breakout for a couple reasons:
- Breakout was my favorite Atari game. I grew up during the Nintendo era of video game history, so the Atari 2600 console was a little before my time. But games like Breakout felt timeless in their appeal. I also remember being blown away by some sort of "fancy" Breakout version in the arcade. I only recently found out that was a game called Arkanoid.
- I already had most of the core components of the game figured out like the ball, paddle, and game window. With the basic mechanics in place, I'd be able to focus on other fun features like particle effects, pixel art, animation, music, etc.).
🤔 What's Interesting About Breakout?
Breakout consists of similar elements we saw last time like a game window, a paddle, and a ball. While Pong was meant as a two-player game, Breakout could be played solo since the objective was to break through the rows of stationary bricks at the top of the screen.
Beyond the gameplay itself, Breakout has an interesting history. Before Steve Jobs went on to found some company called Apple, he worked on Breakout at Atari. The story goes that he would invite his friend Steve Wozniak, who worked at Hewlett-Packard, to help him design and build the game. I find it fascinating to think that Breakout had some influence on the design of the Apple II computer, given its place in the history of computing.
If you're interested in reading more about this, the Breakout Wikipedia entry is a great place to start. And there are some fun videos on YouTube like Before Apple: Steve Jobs at Atari from Gaming Historian.
But what's most interesting to me about Breakout is how we can layer features and "game feel" elements on top of the simple core mechanics.
🤯 Game Feel and "Juice"
When I started working on a Breakout implementation, I knew I already had most of the features I needed for the game window and the paddle and the ball. You can see my previous blog post for an overview of how I implemented those features.
I knew I'd have to figure out how to implement the rows of breakable bricks at the top of the screen, but one of the first things I wanted to do was add a "screen shake" feature. When the ball hit one of the bricks, I wanted the window to shake to give the feeling that it was an impactful collision.
A screen shake is one of those features where you kind of don't notice it's there, but you can "feel" it when you play. It's used commonly in games, but proved surprisingly hard to find for browser games.
While looking for resources about how to accomplish this, I stumbled on this talk called The Art of Screenshake, and I became fascinated by this idea of adding seemingly small details to a game that add up to a lot in terms of game feel.
"Just fill your game with love and tiny details."
Jan Willem Nijman
This guided a lot of the work I ended up putting into building Breakout. In addition to the core game mechanics and a screen shake, I added things particle effects, pixel art, and music to make the game more fun and appealing.
For more on the topic of Game Feel, there's also a great video from Game Maker's Toolkit called Secrets of Game Feel and Juice.
🔄 Implementing a Screenshake
So how does a screenshake work? Thankfully we're working with a simple 2D game (as opposed to 3D or VR), so we only need to worry about moving the game window and then putting it back to restore its original position.
We start with our rectangular game window that's rendered with SVG. I've been working with the Elm programming language for these games, so I started with a type and set some initial values.
type alias Window =
{ backgroundColor : String
, x : Float
, y : Float
, width : Float
, height : Float
}
initialWindow : Window
initialWindow =
{ backgroundColor = "black"
, x = 0.0
, y = 0.0
, width = 800.0
, height = 600.0
}
The x
and y
represent the top left position of the game window. What we need to do is shift the x
value to move the window left and right, and shift the y
value to move the window up and down.
Then, after we shift the game window out of place, we restore its position back to the original position at (0, 0)
. Its kind of subtle if you don't know to look for it, but obvious once you see it. Here's what it looks like with the game elements in place so the window shake is visible:
If you go play the game in the production environment, you can actually click anywhere in the game window to simulate the screen shake feature.
I used a random number generator (which can be surprisingly hard to work with in Elm) so the screen shake didn't seem so predictable or jarring. And it does add a lot when the ball collides with a brick to trigger a screen shake.
If you're interested in learning more about screen shakes and more involved implementations, there's an amazing series of talks from the Game Developer's Conference called Math for Game Programmers. One of the talks is called Juicing Your Cameras With Math and it's really fun to see the code you can use to implement your own amazing screen shake features.
🎉 Particle Effects
Next, I wanted to add some particle effects to the game. Particles are usually things like fireworks, explosions, smoke, etc. I already had the screen shake working, but I thought it would be cool to create an explosion of particles when the ball collided with a brick.
I can't take credit for how awesome this feature looks, because I used an existing Elm package that makes these kinds of particles easy to work with. elm-particle
is a package from Brian Hicks, who not only organizes the yearly Elm conference called elm-conf, but is also the nicest person you'll ever meet.
Here's an animation that shows what the particles look like during gameplay:
You can tinker with all sorts of settings for the particles. I ended up spawning ten small particles from the top left of the ball whenever it collides with a brick. In addition to the position, you can also tinker with things like how gravity affects the particles, how quickly they move and rotate, and how long they last before going away.
If you're interested in seeing more of the specifics of how the particle effects are implemented, check out the Breakout.elm
file in the source code. The elm-particle
package documentation also has some fun examples of how to use it for fun things like water and fireworks.
🎨 Assets and Pixel Art
For the Pong game I worked on, all the elements of the game were just based on simple shapes. So SVG rectangles were sufficient to represent things like the game window and the paddles and the ball.
But for Breakout, I wanted to incorporate some pixel art assets into the game to make it look a little nicer. I ended up using an iPad App called Pixel Studio to create the assets, but you might also want to check out Aseprite if you're interested in creating pixel art.
So instead of using Svg.rect
to create a rectangle, I used Svg.image
and linked to the static assets in the repository. The viewBall
and viewPaddle
functions use familiar attributes for the size and position, and also use the xlinkHref
attribute to pull in the pixel art images.
viewBall : Ball -> Svg msg
viewBall ball =
Svg.image
[ Svg.Attributes.xlinkHref "/images/pixel-ball.png"
, Svg.Attributes.x <| String.fromFloat <| Util.Vector.getX ball.position
, Svg.Attributes.y <| String.fromFloat <| Util.Vector.getY ball.position
, Svg.Attributes.width <| String.fromFloat ball.width
, Svg.Attributes.height <| String.fromFloat ball.height
]
[]
viewPaddle : Paddle -> Svg msg
viewPaddle paddle =
Svg.image
[ Svg.Attributes.xlinkHref "/images/pixel-paddle.png"
, Svg.Attributes.x <| String.fromFloat <| Util.Vector.getX paddle.position
, Svg.Attributes.y <| String.fromFloat <| Util.Vector.getY paddle.position
, Svg.Attributes.width <| String.fromFloat paddle.width
, Svg.Attributes.height <| String.fromFloat paddle.height
]
[]
Mozilla's Developer Network (MDN) is great for looking up how to work with SVG elements and attributes. And then it's just a matter of translating it into Elm using the elm/svg
package.
🎮 Updating the Game Platform
I ended up spending some time creating a new back-end for this project as well. I had originally built Pong and used a tool called Parcel to bundle the project. But I ended up running into trouble as soon as I started working with static assets, so I replaced it with the Phoenix Framework, which is way more familiar to me.
It may be overkill to use Phoenix to serve a couple of small image files, but it also gives me some freedom to add back-end features like tracking scores as I build more games. I've also been playing with Phoenix's LiveView capability and love working with it:
Given that I was going from one game to multiple games, I wanted to add an index page to serve as a game selection screen. I also added some simple routing features to navigate between the different games. And I imported the Tailwind CSS framework to start with a simple design and add a footer area with some helpful links.
🎼 Music
For Pong, I had a couple of files called beep.wav
and boop.wav
that I was using for simple sound effects. I left those out of Breakout, but I wanted to add some background music.
In the past, I had tinkered with creating some custom "chiptune" style music using programs like PICO-8 and Bosca Ceoil. They can be fun to play with, but my Breakout implementation was already getting complicated enough, so I tried finding existing music online.
After Googling a bit, I came across PlayOnLoop.com and found a royalty-free, upbeat song I liked and created a new CREDITS.md
file.
To play the music, I use the howler.js JavaScript library. It required using Elm ports to sync up the JavaScript and Elm code, but howler.js has some great features that I'll likely continue to use as I create more games.
🧱 Implementing the Bricks (Finally)
The rows of breakable bricks at the top of the screen were the most important feature, so naturally I procrastinated on implementing them.
Somewhere in the commit history on GitHub, there was a range of commits where I used various combinations of several data structures (List
, Dict
, Array
) in hopes of finding something that felt right to work with. I won't bore you with the details, but I can at least share how some of the fun parts work.
🏗 Individual Bricks
This is what the fields look like for the Brick
type along with the data for a default brick:
type alias Brick =
{ color : String
, height : Float
, hitCount : Int
, hitThreshold : Int
, position : Vector
, strokeColor : String
, width : Float
}
defaultBrick : Brick
defaultBrick =
{ color = "white"
, height = 16
, hitCount = 0
, hitThreshold = 1
, position = ( 0, 0 )
, strokeColor = "black"
, width = 80
}
One of the first things that might stand out is the position
. For Pong, I was using separate x
and y
fields to store Float
values. But I've been working towards learning Linear Algebra a little better for the next games that I plan to work on.
For Breakout, I used a custom Vector
type, but for the next games I work with I'll likely use the elm-explorations/linear-algebra
package. Khan Academy also has great lessons on Linear Algebra if you're looking to learn more.
The other thing you might have noticed is that I added hitCount
and hitThreshold
fields. This allows for setting some breaks as "hard" bricks that take multiple hits to break.
🤔 Data Structures
After some initial thrashing about which data structure to use, I settled on using a Dictionary because it would allow for identifying bricks by their row and column. In this type alias, we create a Dict
of bricks where the two Int
types represent the row number and column number.
type alias Bricks =
Dict ( Int, Int ) Brick
The implementation for the bricks may have ended up somewhat complicated, and it's possible a normal List
would have sufficed. But I knew I wanted to adjust things like the hitCount
and color
for specific rows or bricks, and using a Dict
made that a little easier.
Here are some of the important functions I used for constructing the rows of bricks.
buildRow : Int -> String -> String -> Bricks
buildRow rowNumber color strokeColor =
List.range 1 10
|> List.foldr (insertBrick rowNumber) Dict.empty
|> setRowColors color strokeColor
|> setRowPosition
insertBrick : Int -> Int -> (Bricks -> Bricks)
insertBrick rowNumber columnNumber =
Dict.insert ( rowNumber, columnNumber ) defaultBrick
I'm glossing over the details, but definitely check out the Breakout.Brick
module if you want to see how it all comes together.
🎨 Styles and Animation
For the brick colors, I used the color palette from Tailwind CSS and looked up the corresponding hex values to create a "ROYGBIV" rainbow for the rows.
The "hard" row of bricks at the top takes multiple hits to break. So it has a different stroke color as a visual indicator that there's something different about those bricks, even if it's not obvious until the ball hits them.
Another cool feature that isn't obvious is that the opacity of each brick is calculated as the percentage of hits remaining to break the brick. In other words, if we made a brick that took ten hits to break, the brick would start out opaque and subtract 10% of its opacity after each hit. If we were to create new levels, this would be a great feature to consider.
One of the most fun features to add was the drop-in animation for the bricks when the game first loads. The Animate.css library has tons of amazing examples for this kind of thing.
I used those examples to create a "bounceInDown" animation that you can find in the assets/css/animations.scss
file. Then, in the Elm code, I added that bounce-in-down
class to the bricks to get the animation working.
✨ Wishlist
It's amazing how many cool features you can add to a small game like Breakout. But I wanted to find a good stopping point and move on to a new game. One of the big takeaways from this project was how distracted I can get while trying to finish something. I actually finished writing the code for the game months before I was able to publish this blog post.
But there are some known bugs and wishlist items that would be great to add to the game:
- Create Multiple Levels
- Improve Collisions
- Add Sound Effects
- Incorporate Randomness
- Save High Scores
- Expand Particle Effects
If you're looking for inspiration and more features to think about, here are some other great places to look:
📈 What's Next?
I've been having fun building a little arcade cabinet lately. At some point, I'd love to see one of the games I build running on it.
I've also loved getting to explore things like WebGL and Unity recently. But the next game I'm going to focus on building is the classic game Adventure. And my plan is to hopefully incorporate both 2D and 3D features if I can get it all figured out.
If you're learning functional programming and game development, feel free to get in touch with me and I'd be happy to hear from you. You can also find me on Twitter.
And here are links to the game and the source code!
Top comments (1)
Nice! I beat the game. :)