Run Time Examples

Overview

The runtime API is split in to two main access points the `Shapeshifter` class, for generating worlds and configuring data. And the `FLX` class, for prompting and interacting with the AI.

Getting Started

To get started is very easy, you simply need to set the Library that assets will be pulled from, and the generators or generator stack that will be used. Then generate!

using UnityEngine;
using Moonlander.Core;

public class SetupAndGenerateExample : MonoBehaviour
{
    public Library library;
    public GeneratorStackPreset generatorStack;
   
    public Bounds generationArea = new Bounds(Vector3.zero, new Vector3(50, 15, 50));
        
    private void Start()
    {
        Shapeshifter.Library = library;
            
        Shapeshifter.InstantiateGeneratorStack(generatorStack);
            
        Shapeshifter.GenerateAreaAsync(generationArea);
    }
}

You only need to set the library and generator stack once, Shapeshifter.GenerateAreaAsync can then be called repeatedly from anywhere on the main thread to generate multiple areas.

If you set the Library in the editor via the overlay, and set the generators via the Generators window. You can just call Shapeshifter.GenerateAreaAsync at runtime without any setup.

Using Copilot FLX

All AI interaction in Moonlander is done through the FLX assistant(pronounced Felix). With the FLX class being the main access point.

To get started, you need to first create a Chat, a Chat is a local copy of the back and forth conversation with FLX. It also acts as a unique key for sending messages and deleting chats.

Chat myChat = await FLX.CreateChat()

Most methods in FLX have overloads for either raising a callback when completed, or using an async/await pattern. You can use whichever works best for you!

Once you have created a Chat, you can send a message on that Chat to FLX.

FLX.SendMessage(myChat, "A pine tree forest.")

If FLX responds with a Generator Stack, it will be extracted from the message and automatically instantiated, replacing any previous generators.

Multiple messages can be sent to the same Chat to continue the conversation FLX.SendMessage(myChat, "Add some ferns too"). However you must wait for a response before sending another message to the same chat.

Chats are not stored locally at runtime, so when exiting a runtime environment (either play mode, or closing the application in a build), it is important to delete the chats that were created. Otherwise they will stay on the server with no way to get or delete them.

FLX.DeleteChat(myChat)

A Chat can be serialized and deserialized like any other C# object, so can be integrated in to a custom save system for storing them between runtime sessions.

When saving locally, you do not want to delete the chat, as doing so would remove it from the server and it cannot be restored. Even if you have the local copy.

The following example creates a new Chat, sends a prompt to FLX requesting a pine forest, and after it gets a response and has applied a stack, deletes the Chat.

using UnityEngine;
using Moonlander.Core;

public class ChatExample : MonoBehaviour
{
    public string prompt = "A pine tree forest.";

    private async void Start()
    {
        Chat quickChat = await FLX.CreateChat();

        await FLX.SendMessage(quickChat, prompt);

        FLX.DeleteChat(quickChat);
    }
}

Generally it is better to keep a reference to a Chat and send it new prompts instead of creating and then deleting them.

Using Shapeshifter and Data Registries

Data Registries hold data that Generators can use and have generated. As such, it can be helpful to access them for gameplay.

The DataRegistryRef<T> class is the easiest way to get a reference to a Data Registry that is being used by a generator. To use it, you specify the type T of the IDataRegistry that you want to get a reference to, and specify the name of the DataRegistry. The type combined with the name, act as unique identify for a DataRegistry. No two DataRegistry of the same type can also have the same name.

The name of the DataRegistry is case sensitve, so "terrain", and "Terrain" would be two different registries.

Once you have setup the DataRegistryRef<T>, you can resolve the reference at any time by calling the Resolve() method. For example:

myRegistry = registryRef.Resolve()

The following example sets up a reference to a Terrain DataRegistry, then resolves the reference, and if a DataRegistry is found, logs the terrain height at the GameObject's position.

using UnityEngine;
using Moonlander.Core;
using Moonlander.Core.DataRegistries;

public class GetDataRegistryExample : MonoBehaviour
{
    public DataRegistryRef<TerrainDataRegistry> terrainDataRegistryRef = new DataRegistryRef<TerrainDataRegistry>()
    {
        RegistryName = "Terrain" 
    };

    private void Start()
    {
        TerrainDataRegistry terrainRegistry = terrainDataRegistryRef.Resolve();

	if (terrainRegistry != null)
        {
 	    float terrainHeight = terrainRegistry.GetHeight(transform.position);
        
	    Debug.Log($"Height: {terrainHeight}");
        }
    }
}

Some Generators automatically output DataRegistries, while some have a text field where you need to set the name of the DataRegistry that it will output.

The ObjectPlacementGenerator for example has a Output text field. Giving it a name will cause the Generator to put all of the objects it generates in to a ObjectDataRegistry with the same name.

Multiple Generators can output to the same DataRegistry.

Last updated