-
Notifications
You must be signed in to change notification settings - Fork 108
Hooks
NOTE Until our documentation settles down, USAGE.md is a better source of up to date information
This part of Arcadia is under active development. We're documenting the parts that are most settled, but expect changes.
Unity will notify GameObjects when particular events occur, such as collisions with other objects or interaction from the user. They call them "Messages" and most are listed here. In C# Unity development, you specify how to respond to messages by implementing Component classes and attaching instances of them to GameObjects. Arcadia is different in this regard. Instead, you can "hook" Unity messages on GameObjects to Clojure functions. We've found this to be a better fit for Clojure's live coding and functional programming emphasis while continuing to work stably with Unity.
As an example, we will make the main camera rotate over time or, more specifically, we will react to the Update message by calling Rotate
on the Camera's Transform
component. The code assumes you have already invoked (use 'arcadia.core)
. Hooks are attached with hook+
, which expects the GameObject to attach to, the message keyword, and the function to invoke in reaction to the message.
(hook+
Camera/main ;; the GameObject
:update ;; the message
(fn [go] ;; the function
(.. go transform (Rotate 0 1 0))))
Hook functions take the attached GameObject as a first argument, then any additional message-specific arguments after that. Update does not have any additional arguments.
Any reference that implements IFn can be used as the function, which means anonymous functions and function short hand, as in
(hook+
Camera/main
:update
#(.. % transform (Rotate 0 1 0)))
Importantly, this also includes symbols referring to functions, and – importantly – Vars.
(defn rotate-camera [cam]
(.. cam transform (Rotate 0 1 0)))
(hook+ Camera/main :update rotate-camera)
(hook+ Camera/main :update #'rotate-camera)
The difference between the last two lines is that the first one will attach the current value of the rotate-camera
function to the camera, while the second one will attach the Var, meaning the actual function is looked up on every invocation. That means if the function is changed e.g. in the REPL, camera's behaviour will also change. This is a major part of our live coding experience.
By the design of Unity, messages are always sent to GameObjects, even "global" seeming messages like OnApplicationQuit. There is no way to handle a message without attaching behaviour to a GameObject.
As another example, we will attach a hook to objects to log information about their collision with other objects. This is a common physics debugging technique.
(hook+ (object-named "Cube")
:on-collision-enter
(fn [go collision]
(log (.. go name)
" collided with "
(.. collision gameObject name)
" at velocity "
(.. collision relativeVelocity))))
This will start logging the collisions of an object named "Cube" in the scene. Note that Arcadia hooks rename Unity's camel case names to more idiomatic Clojure hyphenated keywords with (OnCollisionEnter
becomes :on-collision-enter
). Also note that as per OnCollisionEnter's documentation, the message includes a parameter, so our function takes two arguments: the attached object (go
, the same as all Arcadia hook functions), and the collision information (collision
, of type Collision). We use interop to extract relevant data and log it.