A physics based 2D side scrolling action game written in Clojurescript/WebGL.
If you find the game joyful or the source code useful please consider donating : DONATE
(Controls : F - Punch D - Block S - Kick V - Shoot Cursor keys - movement)
You rent a tiny flat in an Eastern-European-style block of flats. You want to visit your girlfriend but you see gang members on the road. They take your money and food every time. Today you won't let this happen!!!
The development of Brawl started in 2004 after I checked out Keith Peters' (bit-101.com) awesome inverse kinematics experiments written in flash.
I immediately imagined a side scroller game where actors can punch and kick each other and they react and fall very lifelike thanks to inverse kinematics.
The first prototype was ready in December 2004, I immediately uploaded it to gotoandplay.it, it is still here.
Then in 2010 I picked up the project again and created a prototype for iOS. It remained a prototype until 2018 when I finally finished it in C/OpenGL and released it for iOS, Android, Mac, Windows, Linux and HTML5.
Then in 2020 I rewrote it in clojure/script and I'm so satisfied with the language that this will be the maintained/live version of the game.
There are not many games written in clojure, especially not real-time action games with WebGL, UI/font rendering, mass point dynamics, inverse kinematics and skeletal animation but I had to make one.
I was curious about two things :
- how do you structure a complex game like this in clojure, a super compact stateless lisp language?
Clojure code Compared to the C equivalent of the game :
Clojure - 7000 lines, 210000 characters
The clojure code size is roughly the third of the C code size.
Most common pattern dilemmas
One complex reducer function or multiple simple threaded reducers ( aka transducers )?
The second solution wins in clarity and readability altough it needs multiple iterations instead of one. If you also filter out items with every step then the speed should be almost identical.
Extract sub-data for a function or pass the whole dataset and let the function extract and re-insert data for itself
It's hard to decide. In higher parts of a program the second version results in a cleaner code, caller functions are cleaner and callees are all right but in case of lower parts it is overkill, for example you don't want to extract x and y components of vectors from the whole state in an add-vector function.
Destructure right in the function's parameter list or destructure in the head of the function in a let block
The first solution is tempting but after a certain complexity the parameter list becomes very bloated and unreadable meanwhile the second solution produces super clean code, you can destructure sub-maps row by row and the line count is only one row bigger (with the original parameter list)
- cljs_webgl - for clojure-style opengl state handling
- tubax - for svg/xml parsing
The UI renderer and the Mass Point Dynamics Engine I wrote for the game are also open-sourced separately :
The svg parser is pretty simple but usable, it is in src/brawl/svg.cljs
- Install clojure and shadow-cljs
- Check out the repo
- Start the app with :
shadow-cljs watch app
- shoot sound missing
- add punch/kick timeout to normalize maximum hit count
- increase enemy count/ai toughness after first walkthrough
- longer kick
- better ai proximity
- K.O. by XY on wasted screen
- better aspect ratio on mobile devices