Introduction to quill, a minimal unity ui framework with lua modding support | devlog_0
Tags: csharp, unity3d, gamedev, lua
Published: 2021-07-18 08:10:50

## Motivation
As a developer we craft interface elements and since so many visual element involved in the process, our engine would have a graphical view or tool for help you to handle shapes, color and layout. After that, when you add logical behavior to these elements you have to deal a reference hell. 

In unity, visual ui objects can be created in canvas and can be modified via their component. Later you would save those as a prefab and reference them to another component. But at some point, things start to get complicated. Some prefabs may needed on the fly, so `Resources` would be handful. 

But how other engines approach to graphical aspect of creation user interface?

* Win Forms have a design editor built in to visual studio BUT it has a XAML option.
* Similar to that, android studio has a design tool BUT it has a XML output.
* XCode has a design tool BUT now they got SwiftUI.
* And there is Flutter and React Native as declerative flow.

So these engines/frameworks has option to drive user interface programming from code.

Returning to unity, when you have a prefab, unity creates a YAML file, but you wouldn't want to mess with it. 
 
Nevertheless, In theory, you would create any `gameObject` by creating and adding necessary components in runtime. But it would take some effort. At this point, I asked myself, why not create a wrapper api for that?

Recently, I played around with LÖVE2D framework and I love it! You wouldn't believe it how it's easy to setup and draw something to the screen.

Here is a snippet from LÖVE2D. Similar to `OnGUI` in unity, `love.draw` will execute block and draw graphic related stuff. It's that easy. 
(Also I'm aware of the old IMGUI api, but unity left and moved on with UnityUI).

```lua
function love.draw()
    love.graphics.rectangle("fill", 20, 50, 60, 120 )

    love.graphics.setColor(0, 1, 0, 1)
    love.graphics.print("This is a pretty lame example.", 10, 200)
end
```

There are plenty of example after I researched:  
[LÖVE2D](https://love2d.org)
[LIKO-12](https://liko-12.github.io/#/)
[Pixel-Vision](https://pixelvision8.github.io/PixelVision8Website/)(MonoGame based, also uses MoonSharp)
[TIC-80](https://tic80.com)
[PICO-8](https://lexaloffle.itch.io/pico-8)
[Raylib](https://raylibtech.itch.io)
[Defold](https://defold.com/manuals/gui/)
[Some MonoGame libraries](https://github.com/aloisdeniel/[awesome-monogame#user-interfaces)

So this was the motivation of my how I started to make Quill, a UnityUI wrapper api to create user interface in runtime with lua scripting option. Although its not a pro feature api, its more than a pleasure to spend time with it.

Let's take a look how its going so far.

## Quill C# API

Quill's building blocks are just standart UnityUI components.

In Unity, When you create a ui object in a scene, if there is none, it'll create a Canvas and InputSystem object first, then your object goes as a child to Canvas.

So, when Quill initilaze, it'll create a Canvas and InputSystem.

```csharp
private void Start()
{
    Quill.Init();
}
```

It would be possible to use any Canvas that exist in the scene or hand crafted prefabs could be nested in Quill's canvas, since it holds a reference for canvas

```csharp
...
Quill.mainCanvas.sortingOrder = 100;
Quill.mainCanvas.renderMode   = RenderMode.WorldSpace;
...
```

### Elements
```
* QuillElement (Empty)      //  Wraps RectTransform and provides base functionality
* QuillLabel                //  Derives from Text
* QuillBox                  //  Derives from Image
* QuillButton               //  Derives from Button
```

```csharp
private void Start()
{
    Quill.Init();

    var box     = Quill.CreateBox(Color.red);
    Quill.mainRoot.Add(box);
    box.SetSize(200, 200);
    box.SetPosition(50, -50);

    var label   = Quill.CreateLabel("hello world");
    box.root.Add(label);
    label.SetPosition(50, -50);
}
```

![sample1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0li79n8so37fw4djh1xi.png)

All elements implement `IQuillElements` so base functionality of `RectTransorm` is available for each elements.

```csharp
QuillButton button;

private void Start()
{
    Quill.Init();

    var box = Quill.CreateBox(Color.red);
    Quill.mainRoot.Add(box);
    box.SetSize(300, 300);

    button = Quill.CreateButton("hello button");
    button.box.color = Color.blue;
    box.root.Add(button);
    
    button.box.SetSize(200, 40);
    button.box.SetPosition(50, -50);
    button.onClick.AddListener(OnButtonClick);

    box.SetPosition(50, -50);
}

private void OnButtonClick()
{
    button.label.text = "this button clicked";
}
```

![sample2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/biy0q81wh8b2emuwee6o.png)

And finally, a broadcast option to handle events

```csharp
//  ScoreManager
int score;
private void Update()
{
    score += 5;
    if (Input.GetKeyDown(KeyCode.Space))
    {
        var data = new MessageData("score");
        data.container.Add("playerName", "John");
        data.container.Add("playerScore", score);
        Quill.message.Post(data);
    }
}
```

```csharp
//  ScoreView
QuillLabel scoreLabel;
int currentScore;

private void Start()
{
    scoreLabel = Quill.CreateLabel("score: " + currentScore);
    Quill.message.Register(MessageListener);
}

private void MessageListener(MessageData data)
{
    if (data.id == "score")
    {
        //  handle event
    }
}
```

## Quill Lua API

Lua lang is widely used for as scripting and modding tool in game development. I wanted to explore lua ecosystem and idea of modding unity projects. Thanks to [MoonSharp](moonsharp.org), running lua code is fairly easy.

Lua API is completely optional. After `Quill` initialization, `QuillLua` takes step in.

```csharp
private void Start()
{
    Quill.Init();
    QuillLua.Run();
}

private void Update()
{
    QuillLua.OnUpdate();
}

private void OnDestroy()
{
    QuillLua.Exit();
}
```

And like `Resources` folder in Unity there is another special folder `StreamingAssets`. This folder will be available after built. `QuillLua` will read all lua files placed in `StreamingAssets/LUA` directory and these special functions will be called, accordingly.

```lua
function OnInit()

end

function OnUpdate(dt)

end

function OnMessage(data)

end

function OnExit()

end
```

`OnMessage` callback listens for events dispatched from c# api. Any data added to message data container will be coverted to a lua table

```csharp
...
var data = new MessageData("on_player_score");
data.container.Add("score", 3);
data.container.Add("player", "cemuka");
Quill.message.Post(data);       //  this is opttional, if scope is lua
QuillLua.MessagePost(data);
...
```

```lua
function OnMessage(data)
    if data.id == "on_player_score" then
        quill.log(data.container.player)
        quill.log(data.container.score)
    end
end
```

Lua api is a wrapper too, it bridges around Quill and MoonSharp with little required tweaks. This way all components available for lua api.

Here is a complicated example, a button with a label. After click, it will execute `clickHandler` function. Its label will hide after 3 seconds.  

```lua
local button        = nil
local timer         = 0
local timerStarted  = false

function cilckHandler()
    button.label.setText("button clicked")
    local buttonColor = button.box.getColor()
    buttonColor.a = 0.3

    button.box.setColor(buttonColor)

    timer = 3
    timerStarted = true
end

function OnInit()

    root    = quill.mainRoot()
    
    color = {}
    color.r = 0.4
    color.g = 0.8
    color.b = 0.3
    color.a = 1
    
    button = quill.button("this is a button")
    button.box.setColor(color)
    button.onClick.add(cilckHandler)
    
    root.addChild(button)
    
    button.setPosition(20,-200)

end

function OnUpdate(dt)

    if timerStarted then
        timer = timer - dt

        if timer < 0 then
            button.label.hide()
    
            timerStarted = false;
        end
    end

end
```

![click](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g766izfu1pkz159fbrp9.gif)

### Fonts

After a quick search, I found that you can't load a font directly into unity. Happily  you can load installed fonts in OS. That said, if there is no font found, it'll fallback to default font.

Please, let me know if you have further info on this topic. 

```lua
...
root    = quill.mainRoot()
local firaCode = "Fira Code"
quill.loadFont(firaCode, 24);          

local label1 = quill.label("label1")    -- default font (Arial)
local label2 = quill.label("label2")    -- default font (Arial)
label2.setFont(firaCode)                -- Fira Code

quill.setDefaultFont(firaCode)          -- now default font is Fira Code


local label3 = quill.label("label3")    -- default font (Fira Code)

root.addChild(label1)
root.addChild(label2)
root.addChild(label3)

label1.setPosition(100, -100)
label2.setPosition(100, -150)
label3.setPosition(100, -200)
...
```

![font](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hco8oyrmt6uq2tix6kbh.png)

### Images

Also another folder `QuillLua` will look up is `StreamingAssets/IMAGE`. 

```lua
...
local box = quill.box()
box.setColor(color)
box.setSize(300, 100)
box.sprite("icons/body.png");
...
```

You can have full control on sprite creation. 9-Sliced supported.

```lua
...
options = {}
options.filterMode = "Point"
options.pivotX = 0.5
options.pivotY = 0.5
options.extrude = 0
options.pixelsPerUnit = 100
options.borderX = 3
options.borderY = 3
options.borderZ = 3
options.borderW = 3


local box = quill.box()
box.setColor(color)
box.setSize(300, 100)
box.sprite("body.png", options);
box.setImageType("Sliced");

root.addChild(box);
box.setPosition(100,-100)
...
```

![sliced](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3jcxuds8uwbtgh02pp7.png)

### Modules

Modules are supported.

```lua
-- lib/test.lua
test = {}

function test.greeter()
    quill.log("hello modules")
end

return test
```

```lua
-- main.lua
local module = require("lib/test")

function OnInit()
    module.greeter()
end
```
##  Closing words

I love creating and engaging user interfaces, mostly games and tools. 

Quill is such an experimental and heavily work in progress project for me. While I'm learning and researching for other modding mature apis (i.e. wow), I'll try to complete other ui components. Besides, I'll start to documenting the api and will post lots of examples.

Thanks for reading, feel free to contribute or ask a question.

[https://github.com/cemuka/Quill](https://github.com/cemuka/Quill)