selfsame's pages

git itch games tilde soundcloud instagram artstation

Clojure in Godot with ArcadiaGodot

godotclojure

I'm announcing a 1.0 release of github.com/arcadia-unity/ArcadiaGodot, which brings Clojure into the Godot Game Engine. This release has a focus on usability and includes all the tools needed to interact with Godot's scene trees, signals, and node lifecycle methods. It also includes Arcadia's nRepl implementation and a filewatching feature to reload .clj files as they change.

ArcadiaGodot is an offshoot of github.com/arcadia-unity/Arcadia which brought Clojure into Unity. Huge thanks for their continued support and mentorship, and for allowing this project to be hosted under the arcadia-unity github organization.

If you want to jump in the README.md covers setup and the arcadia namespaces are a pretty quick read.

So what's New?

There's a set of utility fns in arcadia.core for working with the scene tree, including change-scene, load-scene, instance, root, get-node, find-node, add-child, remove-child, parent, children, destroy, and destroy-immediate.

An example of instantiating something into the current scene:

(use 'arcadia.core)

(let [player (instance (load-scene "nodes/player.tscn"))]
  (add-child (root) player))

Hooks

Godot is an object orientated game engine. Everything on the scene tree is a class that inherits from Node at some level, including the user scripts that run your game logic. Clojure doesn't really have class emission at runtime so Arcadia's approach is to use "hooks" that route an objects's methods to arbitrary functions. In addition ArcadiaGodot's hook fns invoke with their parent as the first argument, not themselves.

ArcadiaGodot's runtime hook+ and hook- have the same usage as Arcadia's

(use 'arcadia.core)

; add a anonymous hook function to the player's _Input method
(hook+ (find-node "player") :input :user-key 
  (fn [o k e] 
    (log "input event:" e)))

; remove a hook fn on the menu node
(hook- (find-node "menu") :ready :my-key)

You can hook into :enter-tree :exit-tree :ready :process :physics-process :input :unhandled-input. The process methods take an extra float argument of elapsed delta time and the input methods take an InputEvent argument.

Signals

Signals are Godot's event system, allowing Godot objects to send messages that invoke methods on connected objects. It's a nice way to decouple objects, where you just emit signals to whatever might be listening rather than keeping tabs on object references. You can define your own signals, and there's a lot of useful ones on the built in nodes.

Here, like with the hooks, ArcadiaGodot's signals connect to arbitrary functions.

(connect (find-node "Button") "pressed" (fn [] (change-scene "scenes/level1")))

User defined signals can have additional arguments of any of Godot's variant types:

(add-signal (find-node "player") "hurt" System.Single Godot.Vector2)

(emit (find-node "player") "hurt" 4.5 (Godot.Vector2. -1 0))

State

ArcadiaGodot has a simple state feature to help store data with nodes (just a .state field on ArcadiaHook) which can be accessed with state, set-state, and update-state.

(let [ball (find-node "ball")]
  (set-state ball {:foo 1})
  (state ball)
  ; {:foo 1}
  (set-state ball :foo 2)
  (update-state ball :foo inc)
  (state ball :foo)
  ; 3
  )

Tooling

There is now a file watcher (enabled by default in configuration.edn) that reloads .clj files when they are changed.

ArcadiaGodot includes three (3!) repl implementations (ports configured in configuration.edn). The UDP repl (this was an Arcadia thing for some time), a socket repl, and an nRepl. Hopefully this covers the majority of editor requirements.

Other fun stuff

arcadia.core has a timeout fn for delayed code execution, tween for using Godot's Tween functionality, and play-sound for easy audio.

(timeout 2.0 (fn [] (log "delayed hello!")))

(tween (find-node "block") "rotation_degrees" 0 -90 0.5 {:transition :circ :delay 0.3})

(play-sound (rand-nth ["sound/shoot02.wav" "sound/shoot02.1.wav" "sound/shoot02.2.wav"]))

Taking it for a spin

I took the opportunity to make something with ArcadiaGodot for the 2020 Autumn Lisp Game Jam, and came up with a little hybrid breakout / shooter arcade game caled Orbomancy.

orbomancy gif

Development felt pretty good! Godot's editor is really stable and has intuitive tools for working with the scenegraph. It's very easy to break things up into .tscn fragments that you instantiate with code. There's a few quirks where the game runtime crashes when you try to access a destroyed node (you can use arcadia.core/obj to avoid that) but other than that everything came together smoothly. I'm excited to see further updates to the Godot engine and to have this setup as my go to for future projects!

You can grab the full project files for Orbomancy at https://notabug.org/selfsame/orbomancy