Nullable Fields in Monobehaviours
A lot of Unity scripts involve classes that inherit from MonoBehaviour, some of which will have fields that become settable properties in the Unity editor. Because of to the way Unity does serialization, the values are not passed to your class' constructor. (Unity wants you to use a no-argument constructor, if any; you shouldn't be calling any Unity API methods from it.)
As a result, you need fields that initialize to
null, which Unity will then in turn set. Since F# is designed to interoperate with .NET, it foresees this eventuality (while screaming at you that it's generally a bad design decision):
namespace My.Unity.FSharp open UnityEngine type MyBehaviour() = inherit MonoBehaviour() [<SerializeField>] [<DefaultValue>] val mutable MyGameObject: GameObject
val mutablewith the
[<DefaultValue>]attribute allows for the field to take on a "zero" value when it is initialized; for reference objects this is
Mutable Struct Fields
Many Unity data structures—
Quaternion, for example—are implemented as .NET structs. You may find yourself bewildered at why this code doesn't work:
let toQuat: Quaternion = Camera.main.transform.localRotation toQuat.x <- 0.0f
If you hover your mouse over the
toQuat.x, the helpful hover tip will tell you
val mutable x: float32. Yet you still get the error
"Error: A value must be mutable in order to mutate the contents or take the address of a value type, e.g., let mutable x = ..."If you read this too quickly, it looks it's telling you
xhas to be mutable, but we've already seen that it is a mutable field.
The answer is that for a value type's fields to be mutable, it itself has to be mutable. So the correct code is:
let mutable toQuat: Quaternion = Camera.main.transform.localRotation toQuat.x <- 0.0f
Dependencies on Scripts in Prefabs
If you happen to be working your way through the HoloLens tutorials, as I have been, you'll reach Chapter 6 of Holograms 101, where you're given code referencing
SpatialMapping.Instance. If you write this in F#, you'll likely see
Error: The namespace or module 'SpatialMapping' is not defined.
The problem is that the
SpatialMappingclass is part of the SpatialMapping prefab asset that you're given in the exercise; the script is built into the prefab. But writing F# code for Unity requires compiling to a plugin, so we need the
SpatialMappingclass to be compiled in order to be able to refer to it.
The solution is actually not difficult. If you build your Unity project, Unity will create an
Assembly-CSharp.csprojfile at the root of your Unity project. This project file corresponds to a .NET project that you can reference as a project reference in your F# project. Once that dependent project's DLL is built, your F# code will be able to pick up the necessary references.
Out Parameters for Complex Method Signatures
Another issue I encountered was that in some cases, Unity's APIs take an
outparameter but don't make it the last parameter in the method signature. While in general F# is smart with .NET
outparameters—you call the method without the
outparameter, and you get back a tuple pairing a boolean with the returned value—I wasn't able to make this work with a long method signature having the
outparameter in the middle, e.g.
public static bool Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);
It's possible that the reason F# can't return a tuple is that the method signature without the
outparameter collides with a different method signature of
Physics.Raycast. In any event, here you have to handle the
outparameter explicitly, so the F# code would look like this:
let mutable hitInfo = Unchecked.defaultof<RaycastHit> if Physics.Raycast(headPosition, gazeDirection, &hitInfo, maxDistance, SpatialMapping.PhysicsRaycastMask) then this.transform.parent.position <- hitinfo.point
Unchecked.defaultof<'T>is used in F# to assign a null value to a binding for a reference object. The binding has to be mutable, and gets passed as an
outparameter using the
As I come up with more gotchas, I'll keep posting...