August 7, 2020

game dev in Clojure?

It all started with a simple, broken tutorial project...

A little context

If you somehow missed the first page of this site or it was too much to read, well now you can get caught up I'm "learning" game development for fun and if that weren't hard enough I'm trying to do it in Clojure but not the JVM implementation no, this is the version that runs on the CLR, .NET baby, and with a library that is so bleeding edge every tutorial written before 2018 is almost guaranteed to be broken

This is a story about one of those tutorial projects. To be fair, the repo does includes the SHA link to the version of Arcadia it works with. That repo is fighter-tutorial

My Mission

See if I could get it working

The result was a success and it turned out to be a good learning experience.

Although I did cheat and created a namespace with all of the functions that I couldn't find the replacements for.

Also clojure.spec decided to change to clojure.spec.alpha so that was the only break outside of Arcadia itself

I haven't really talked a lot about the actual game itself because there really isn't a lot to talk about.

What did you get?

After all the hard work the game itself is just a big 2d monster and a small player who shoots small missiles that don't even hit the enemy. The player and enemy can collide with each other and you can move the ship around with the keyboard. The ship is kind of hard to control but it doesn't matter because you can't get hurt and there is a boundary system to keep you from going too far.

Add Pandas?

last night I played with rigging up a sprite and added a panda to the fighter tutorial.

I had to rewrite the movement logic because pandas and spaceships move a little differently so I was reminded how nice it is to be able to fiddle with functions while the game is running.

The experience

After a grueling couple of hours making a sprite sheet for my panda (All hand drawn with a mouse BTW) in Gnu Image Manipulation Program I was able to import that into Unity and use the sprite editor to chop up the sheet

It was fun playing around with the animations and I got a couple solid ones set up. One for idle and one for running. Last thing to do in the Unity UI was to set up my transition conditions which is done just by connecting the boxes together with sticks like a UML diagram. Then I set up the variable that dictates when it should transition from one animation to the other. "isRunning" seemed like a logical choice, so when it's true my panda should start running and when it's false my panda should go back to panting like it's out of breath

So the sprites are all under one game object called "panda" with a rigid body and animation component and every thing it needs to live in this game. So I drag it from the scene to the prefab folder to create a prefab so I can just clone all that mess when I need it

Okay but like how was the development experience

Now I'm back in Intellij and I have the game running which starts up the Arcadia repl and I have the Cursive remote repl connected to it. Yeah it spits out all kinds of errors whenever I eval something but that's just because Cursive asssumes you're connecting to a JVM repl and both the Unity console logs and Intellij both print the errors but it's fine. The repl complains that you're sending it this other random stuff, but at the end of the day it still evaluates the forms you were trying to evaluate.

The moment of truth has finally come. I had to write new logic for the panda's movement so lets see how it moves. I eval all of the functions and then hook those onto it's update event handler and my panda immediately shoots out of the scene and I can see in the unity ui that it's Y position is dropping at an alarming rate

After some frantic fiddling with the logic, I finally remember what I did wrong. But first, lets take a moment to observe how nice it was being able to experiment with different ideas while the game was running instead of having to stop the game, rewrite some code, press play, observe it didn't work, stop the game, rewrite, etc.

Anyway, it's good to always remember why people are working so hard to get Clojure in Unity to get that repl driven development workflow.

So...It turns out it wasn't my logic that was causing my poor panda to fall through space, I just forgot to set the gravity to zero on his rigid body. Yeah this game doesn't really have a "floor" per se, it's just going to have a background and everyone is going to be running around in 2d space without much regard for physics

The implementation details

I really wish I felt like posting a lot of code snippets and fleshing this out more

TODO: flesh this out more with code snippets and what not...

The interesting part was when I set up the animations. As I said, I have an "idle" animation and a "running" animation so when it's just standing around it's breathing heavy and when you move it, the legs move, and then when you stop pressing the wasd keys it stops and goes back into the idle animation.

Remember the variable "isRunning" that I set up in the Unity Animator UI to control which animation is active? Like how am I possibly going to be able to set that variable in my code? The equivalent C# Code would look something like

anim = GetComponent<Animator>();

//...some logic to determine that the run variable should be true...
anim.SetBool("isRunning", true);
//...an obviously the code to set it to false would be
anim.SetBool("isRunning", false);

Simple right?

Well that C# code lives inside a file called player.cs or something like that and is added to the prefab in unity so any public variables or references show up in the UI

But that's just not how we roll at repl.

This was one of the first things I wish I had grasped when I was first starting out

3 years ago I had never heard of the Unity game engine, I wasn't much of a game developer, my Clojure skills were okay, and my C# skills were good enough but I had no familiarity with Unity's scripting API which is well documented but pretty vast. So why did I try to make games in Clojure using Unity? That's a post for another day.

But the good new is that it's finally starting to click...slowly but surely so I can translate the standard C# way of doing things into our nice dynamic workflow.

Meet "with-cmpt"

How did I put it all together? Well, the Animator got added to my panda prefab as a Component, ya know, because of the whole "entity component system."

but first lets talk about destructuring

no, this post has gone off in too many directions as it is, lets focus...


   (with-cmpt panda [anim Animator]
      (.setBool anim "isRunning" true)
     )

that's it, all those components that are attached to the object you're referencing are easily pulled out like that

so I was able to use with-cmpt to grab the panda's animator and use interop to call the SetBool method for the boolean value I set up in unity's animator ui. So yeah, my clojure code can toggle the animation.

What took you so long?

Some things about a game object are intrinsic. The obvious one is the xyz position. Even an "empty" game object has a transform object that hangs on to all of those values. So naturally when you do basic tutorials those are going to be the things you're starting out with first, just moving some object around.

But to do anything even just a little bit more advanced will require you to navigate into some mirky waters. Yes, you'll need to do some interop. So if you're not familiar with Unity's underlying scripting API how are you going to figure out which Arcadia functions and macros to use? You've read the doc string for with-cmpt before but it all sounded like nonsense and you just now figured out that it means "with component" as in maybe you tell it the unity component class name like "RigidBody2D" and it will bind that component to the alias you put in the square brackets for the game object you passed in and oh gosh, yeah I see how this can be confusing at first.

Now can we talk about destructuring?

yeah, lets talk about destructuring because gosh there is a lot of that going on and it seems to happen like magic or the syntax doesn't make it immediately obvious because macros are magical too

That's a beautiful thing about Clojure, you can let your code write your code but what if that's not always beautiful?

lets take the classic example of writing a macro to allow you to write your arithmetic operations with infix notation instead of polish notation

so you write a macro called "infix" now you can say


   (infix 1 + 2)

and that will expand into (+ 1 2)

well what if you wanted infix to also take an object and a vector of bindings of name to operations which it pulls out of the object based on whichever one it prefers? what would that look like?


(infix obj [p "plus" m "minus" default "FileNotFound"] 1 (either p m) 2)

this will expand to (- 1 2) or (+ 1 2) depending on the object

So it sounds like you were just having an existential crisis when the part that was supposed to be easy for you...just wasn't

Yes, the readability of macros can be tough but layer on the fact that the macro is doing interop with an API you've yet to get a basic understanding of and boy, you have a recipe for an aneurism.

But wouldn't it be funny to document your struggles on the internet for everyone to see and laugh at? Maybe it would be inspirational

It will be interesting to look back on this and I'll either see how far I've come or I'll say "oh wow, the library has changed so much you don't even use with-cmpt any more"

So are you ready to learn about the role system?

I think this is enough for one day, it's almost 1am and I spent so much time setting up this blog I didn't get to do any coding =( I'm not even going to proof read any of this either.

Thanks for reading and we'll see you in the Archives...

Tags: panda-game