Skip to content

CodeSmile-0000011110110111/de.codesmile.luny

Repository files navigation

Luny Logo

Program Unity in Lua

Both Editor and Runtime. All Unity APIs available (eventually).

You expect hot reload to work every time? Go Luny! :)

Simple runtime script example:

local script = ...

script.Awake = function()
    -- create an instance from a Inspector assigned prefab
    script.go = Object.Instantiate(script.InspectorPrefab)
end

script.Update = function()
    -- make it move slowly to the right
    local pos = script.go.transform.position
    pos = pos + Vector3(0.1, 0, 0)
    script.go.transform.position = pos
end

You only need to consider some housekeeping for hot reload. Like not instantiating 'go' anew every time. Or destroy it when the script 'unloads':

script.OnScriptUnload = function()
    Object.Destroy(script.go)
end

Luny 0.5 Features

  • UnityEditor and UnityEngine "Core Modules" available, more to come ...
  • Instant script reload: no compiling, no domain reload
  • Bind custom objects to Lua
  • Inspector editing of script context table values
  • Sandboxing prevents use of potentially malicious calls
  • Lua functions are fully async-awaitable
  • Uses LuaCSharp, a high performance C# implementation of Lua
  • Compatible with all Editor and Runtime platforms
  • Compatible with Auto-Refresh and Enter PlayMode options
  • Supported and maintained, built with passion and dedication

Visit LunyScript.com for more info.

Limitations

Version 0.5 (alpha) has some ways to go. I will focus on solidifying Editor scripting first, then turn to Runtime scripting which is functional but lacks critical features.

Major areas of work:

  • Documentation
  • More editor functionality
  • Essential runtime features (eg execute scripts when instantiating, coroutines, ..)
  • Essential 'System' namespace types (eg File/Path, String, Collections)
  • Expose more Unity assemblies/packages, eg Input, Cinemachine, Physics, etc
  • Improve bindings generator (generic T, ref/out, tuples, operators, more collections)
  • Improve Lua interoperation with C#/Inspector and custom object binding

Please don't hesitate to send requests and to report any issues!

Requirements: Unity 6000.0.35f1 or newer

Since Luny is a new project and I'm just a crazy solo developer, I do not wish to spend time on supporting already out-of-support Unity versions.

MIT License

License: MIT .. as I wish to keep it free and open!

If you share the same sentiment, please subscribe to my Patreon as I continue to pour my worklife-balance into Luny!

Installing Luny

In the Unity Editor:

  • Open Window => Package Management => Package Manager
  • Click top-left [] button and choose Install package from git URL...
  • Enter: https://github.com/CodeSmile-0000011110110111/de.codesmile.luny.git

A word from the author

Luny was a crazy amount of work (~9 months) to just create the infrastructure for Lua in Unity to be the natural alternative to C#. I wish to keep working on Luny since it's downright awesome to iterate without delays, to stay in the flow while coding. To that end, I want you to ..

.. use Luny first of all! Provide feedback, make requests, report issues: all right here or via social channels: Website, Discussions, Discord, X, Email (for personal inquiries). And join my Patreon to stay in the loop.

Let me know what your use-cases are, and how Luny helps, or could be helpful if only it did that specific thing.

Then just follow the most important Luny Club rules:

  1. DO talk about Luny!
  2. DO talk about Luny!

Spreading the word is most helpful! :)

Getting Started with Luny

For this walkthrough I encourage you to have your editor window sized so that you can at least see the Unity Editor's 'Console' window somewhere on the side.

Type often, save often, watch the changes happen in the Console by 'printing' a lot of things. Use the same approach as you explore other uses later. To "log" strings use:

local hello = "World!"

print(hello)    -- Debug.Log
log(hello)      -- same as 'print'
warn(hello)     -- Debug.LogWarning
error(hello)    -- Debug.LogError

Example Editor Script

How about: A Unity Editor utility script that automatically opens new Scene assets?

Exactly the kind of script we often wish for but rarely write because of the friction imposed upon us. Particularly when we need to figure many things out one by one:

  • What class is this callback in again?
  • Oh god .. which of these callbacks to use?
  • Are these static events I can subscribe to or do I have to subclass?
  • Could these parameters be null or empty?
  • What exactly are these string[] parameters anyway?
  • If they are paths, are these absolute or relative?
  • What's the file extension for scenes?
  • How to open a scene, but in the editor?

So that's several minutes wasted on just compiling the C# code multiple times in quick succession. You got actual work to do, remember?

Here's the crazy Luny solution, I'll explain it in detail next:

local script = ...

script.OnPostprocessAllAssets = function(imported)
    if #imported == 1 and imported[1]:EndsWith(".unity") then
        EditorSceneManager.OpenScene(imported[1]);
    end
end

Your First Editor Script

Create a new Editor Lua script in a /Editor folder:

Create Editor Lua Script

Capture the script's context table in a local variable, where ... is Lua's varargs operator:

local script = ...

Implement event functions in the script table, using the same name as the C# callback:

script.OnPostprocessAllAssets = function(imported, deleted, moved, movedFrom)
    print("Asset Postprocessing, imported count: " .. #imported)
end

You can omit unused, trailing arguments for clarity and brevity:

script.OnPostprocessAllAssets = function(imported)
end

Lua understands english, naturally uses 1 as a list's first element, and counts elements with the # prefix:

if #imported == 1 and imported[1]:EndsWith(".unity") then
end

Since imported[1] is a string, we can call a function that operates 'on' a string by using the : (colon) operator:

imported[1]:EndsWith(".unity")

The : operator is syntactic sugar for the more verbose alternative, resembling a C# static method call:

string.EndsWith(imported[1], ".unity")

Both UnityEditor and UnityEngine APIs are at your disposal, these are merely tip-of-the-iceberg examples:

-- static method call:
EditorSceneManager.OpenScene("Main Scene.unity");

-- some vector math:
local input = Vector3.new(10, 20, 30)
local velocity = input.normalized * Time.deltaTime

Saving reloads the script without having to change window focus. Anything in the script context table survives reload:

local script = ...
script.Reloads = script.Reloads and script.Reloads + 1 or 1
print("Reloaded script " .. script.Reloads .. " times")

The .. operator concatenates strings. You may be more curious about the and or pattern though. It's what we call a ternary expression:

(condition) ? (consequent) : (alternative)      // C# ternary expression
(condition) and (consequent) or (alternative)   -- Lua ternary equivalent

The ternary expression is used because all variables default to the value nil. Lua's keyword for null actually means: "value not assigned". The first time this script runs, we will have to assign a number value since we cannot increment a nil value.

You can also write this logic more traditionally:

if not script.Reloads then 
    script.Reloads = 0
end

script.Reloads = script.Reloads + 1 

If you're very accustomed to C languages, you may prefer to use the equality operator. Its inequality counterpart is the ~= operator, which - I promise - you'll get used to:

if script.Reloads == nil then
    script.Reloads = 0
end

A nil value evaluates to false in a boolean condition.

Like in C#, some of us prefer longer over more lines. At least for very short, simple statements it can be a useful formatting alternative:

if script.Reloads == nil then script.Reloads = 0 end

I could show you how you can make multiple statements on the same line, but I do not wish to endorse this style.

If you ever find need for structuring things, use a table. The table is Lua's sole collection type, mimicking both a list and a key-value store. How you format the braces is entirely up to you:

local myTable = {
    tableInsideTheTable = { "This", "Is", "A", "List", "Of", "Strings", "!", },
    TheNumberWeShallCallPI = 3.1415,
    doesAnyOfThisMakeSense = false,
    aDeeperTable =
    {
        name = "Knee-deep",
        evenDeeperTable = { name = "in the Tables" }
    }
}

A table is highly suitable for hierarchical structures, including storing game and configuration data. But unlike certain structured text formats it's actually executable and it supports comments and doesn't mind trailing commas. Above code contains an example. Did you notice?

Some example use-cases for embedded functionality right in the game and config data:

  • generate or initialize content
  • choose which content to load
  • validate, optimize, analyze, format, or convert the content
  • send a webrequest before returning

What you can't do: mining bitcoins on user's devices. Generally speaking.

Most IDEs and text editors support syntax highlighting, error checking, and auto-completion suggestions for Lua, either built-in or via a plugin. Check your editor's plugin manager.

Manual

Work in progress ...

For now it's all in this Google Document.

Will be superceded by Luny-Docs pages. Work in progress.

I also maintain a DevLog for the curious, but more importantly for myself.

Community-Sponsored Project

For years I intended to create something unique and special that satisfies game developers' dirty desires. I think I finally found it! :)

If you agree, join my Patreon to stay informed. Please consider supporting the project with a subscription!

I am confident that Luny can become a fully community sponsored project. It would only take a couple new subscribers at $10 per month on average over the next three years to make that happen. I really wish to keep the project free for all!