"Working Dir Rework" has landed in the private repo (also submitted to the Asset Store). You are now able to keep all your scripts under `{ProjectDir}/OneJS`. And the scripts will be automatically bundled into`{persistentDataPath}/OneJS` for Standalone builds. Docs here have been updated accordingly.

New features and bug fixes will land in the private github repo before becoming available on the Asset Store. To request access, please send an email or pm Singtaa#4915 on Discord with your Github username and OneJS Order/INV number.

UI Dataflow

Your UI code should depend on your core game logic. But your core game logic should not even be aware of the existence of your UI code. So in OneJS context, your JS code will be calling stuff from your C# code, but never the other way around. This one-directional dependency makes everything easy to maintain.

The best way to implement this is via C# events (or similar pub/sub mechanism). Whenever your UI needs something, you can have your core game logic fire an event. And in your JS code, you can subscribe to C# events by appending add_ and remove_ to the event name.

Here's a quick example:

// C#
public class TreasureChestSpawner : MonoBehaviour {  // Set this object to be 'chest-spawner' in ScriptEngine's INTEROP/Objects
    public event Action OnChestSpawned; // Fired when a chest is spawned in the scene
    
    ...
}
// TS
var spawner = require("chest-spawner") as MyGame.ChestSpawner
spawner.add_OnChestSpawned(onChestSpawned)

function onChestSpawned() {
    log("yay!")
}

...

// Event handler can be removed via `spawner.remove_OnChestSpawned(onChestSpawned)`
// Example TS Definition
declare namespace MyGame {
    export interface ChestSpawner {
        add_OnChestSpawned(handler: Function): void
        remove_OnChestSpawned(handler: Function): void
    }
}

You can see this workflow in more detail from the Fortnite UI sample.

Reducing Boilerplates (WIP)

Work is currently being done to reduce some boilerplates. In the near future, you will be able to reduce the following code:

var charman = require("charman")

const [health, setHealth] = useState(charman.Health)
const [maxHealth, setMaxHealth] = useState(charman.MaxHealth)

useEffect(() => {
    charman.add_OnHealthChanged(onHealthChanged)
    charman.add_OnMaxHealthChanged(onMaxHealthChanged)

    onEngineReload(() => {  // Cleaning up for Live Reload
        charman.remove_OnHealthChanged(onHealthChanged)
        charman.remove_OnMaxHealthChanged(onMaxHealthChanged)
    })

    return () => {  // Cleaning up for side effect
        charman.remove_OnHealthChanged(onHealthChanged)
        charman.remove_OnMaxHealthChanged(onMaxHealthChanged)
    }
}, [])

function onHealthChanged(v: number): void {
    setHealth(v)
}

function onMaxHealthChanged(v: number): void {
    setMaxHealth(v)
}

To just:

var charman = require("charman")

const [health, setHealth] = useState(charman.Health)
const [maxHealth, setMaxHealth] = useState(charman.MaxHealth)

useEffect(() => {
    setupEventfulStates(charman, [setHealth, setMaxHealth])
}, [])