Krotox Engine › Docs › Scripting API › Core & Lifecycle

Prefabs, scenes & saving

Spawning and destroying entities, loading scenes (including asynchronously), keeping entities across scene loads, loading assets at run time, saving with PlayerPrefs, SaveGame slots and JsonUtility, and ScriptableObject data assets.

Spawning and destroying

MemberMeaning
Entity Instantiate(Prefab prefab)Spawn the prefab at its saved position.
Entity Instantiate(Prefab prefab, Vector3 position)Spawn at a position.
void Destroy()Destroy this entity and its children.
static void Destroy(Entity entity)Destroy another entity. null is ignored.
public Prefab BulletPrefab;     // drag a .prefab onto this field in the inspector

void OnUpdate(float ts)
{
    if (Input.IsKeyPressed(KeyCode.F))
    {
        Entity bullet = Instantiate(BulletPrefab, WorldPosition);
        Bullet script = bullet.As<Bullet>();   // already past OnAwake and OnCreate
        if (script != null) script.Speed = 20.0f;
    }
}

public override void OnCollisionEnter(Entity other)
{
    other.Destroy();
    Destroy();      // destroying the same entity twice in a frame is fine
}
Destroy() is deferred during Play. The entity is removed at the next safe point: after the OnUpdate pass, after each physics step, or at the end of the frame. Until then it still exists. When it is removed, OnDestroy() runs on every script in the subtree (parent first) before anything is torn down, so OnDestroy can still read its parent, children and components.

Loading scenes

SceneManager.LoadScene("Scenes/Level2.ktx");        // switches after the current frame
SceneManager.ReloadCurrentScene();                   // restart the level

SceneManager.LoadSceneAsync("Scenes/Level2.ktx");   // loads on a worker thread, switches when ready
bool loading  = SceneManager.IsLoading;
float percent = SceneManager.LoadingProgress;
if (SceneManager.HasFailed) Log.Error(SceneManager.ErrorMessage);
SceneManager.CancelAsyncLoad();

string name = SceneManager.GetActiveSceneName();
string path = SceneManager.GetActiveScenePath();

Scene paths. Pass the path relative to the Assets folder ("Scenes/Level2.ktx"), as the Content Browser shows it. That is the recommended form. "Assets/Scenes/Level2.ktx" also works, and on desktop a bare name like "Level2" is searched for, but a built game reads from data.pak, which cannot be searched by name, so ship with full paths. A TargetScenePath field in the inspector stores the right form for you.

A loading screen

IEnumerator LoadWithProgress(string path)
{
    SceneManager.LoadSceneAsync(path);
    while (SceneManager.IsLoading)
    {
        m_Bar.Value = SceneManager.LoadingProgress;
        yield return null;
    }
    if (SceneManager.HasFailed)
        Log.Error(SceneManager.ErrorMessage);
}

Keeping entities across scenes

A scene load replaces the whole hierarchy. Mark an entity to carry it (and its children) into the next scene, for a music player, a game session or an inventory:

[ExecutionOrder(-100)]
public class MusicManager : Entity
{
    public static MusicManager Instance { get; private set; }

    public override void OnAwake()
    {
        Instance = this;
        DontDestroyOnLoad(this);   // or SceneManager.DontDestroyOnLoad(entity)
    }
}
Carried overNot carried over
The entity, its children, its components and its UUID.Dictionaries, other classes and delegates in fields (use static fields for those).
Script field values, private ones too: numbers, bools, strings, enums, plain structs, entity references, and arrays and lists of those.References other scripts hold to the old instance: look the entity up again after a load.
OnEnable runs again.OnAwake and OnCreate do not run again.

If the next scene already contains an entity with the same UUID, that scene's copy is used, so "every scene has a MusicManager" still ends up with one.

Loading assets at run time

Load assets by path when they are not referenced from the scene, such as a skin the player just unlocked. Paths are relative to Assets.

var sprite = GetComponent<SpriteRendererComponent>();
sprite.Texture = "Textures/Skins/Gold.png";              // a path, a handle (ulong) or a Texture2D; "" clears it

Texture2D skin = Assets.Load<Texture2D>("Textures/Skins/Gold.png");   // null if missing
sprite.SetTexture(skin);

Prefab boss = Assets.Load<Prefab>("Prefabs/Boss.prefab");

string path = Assets.GetPath(handle);
ulong  h    = Assets.GetHandle("Audio/boss.ogg");
bool ready  = Assets.IsLoaded(h);
Assets.Unload(h);                                         // anything still using it reloads on next use

Textures stream in the background, so Load returns at once and the image appears a few frames later. To wait for one, use LoadAsync in a coroutine:

IEnumerator SwapBackground()
{
    AssetRequest request = Assets.LoadAsync("Textures/Level3/Background.png");
    yield return new WaitUntil(() => request.IsDone);
    if (request.IsValid)
        m_Background.SetTexture(request.As<Texture2D>());
}

PlayerPrefs: small settings

PlayerPrefs.SetInt("HighScore", 2500);
PlayerPrefs.SetFloat("Volume", 0.75f);
PlayerPrefs.SetString("PlayerName", "Krotox");

int best    = PlayerPrefs.GetInt("HighScore", 0);       // 0 if missing
bool exists = PlayerPrefs.HasKey("PlayerName");
PlayerPrefs.DeleteKey("PlayerName");
PlayerPrefs.Save();

Use PlayerPrefs for settings and small values. For game progress, use SaveGame.

SaveGame: crash-safe save slots

Writes are atomic: a crash or power loss mid-save never leaves a half-written file.

// Key-value data
var data = new SaveData();
data.SetInt("Level", 3);
data.SetBool("TutorialDone", true);
SaveGame.Save("slot1", data);

SaveData loaded = SaveGame.Load("slot1");          // null if the slot does not exist
int level = loaded?.GetInt("Level", 1) ?? 1;

// Raw text, for example JSON
SaveGame.WriteText("slot2", json);
string text = SaveGame.ReadText("slot2");

bool has       = SaveGame.Exists("slot1");
string[] slots = SaveGame.ListSlots();
SaveGame.Delete("slot1");

Saves go to Application.PersistentDataPath: <Project>/Saved in the editor, <exe>/Saved in a Windows build, and the app's private storage on Android.

On mobile, save in OnApplicationPause(bool paused) when paused is true. A backgrounded app can be closed by the system without any further warning.

JsonUtility: structured save data

For saves that outgrow flat keys, turn a plain class into JSON and back. The rules match Unity's JsonUtility.

public class Progress
{
    public int Level = 1;
    public float[] BestTimes = new float[3];
    public List<string> Unlocked = new List<string>();
    public Vector3 Checkpoint;
}

string json = JsonUtility.ToJson(progress, prettyPrint: true);
SaveGame.WriteText("progress", json);

Progress loaded = JsonUtility.FromJson<Progress>(SaveGame.ReadText("progress"));
JsonUtility.FromJsonOverwrite(json, existing);                    // keeps fields the JSON does not mention
if (JsonUtility.TryFromJson(json, out Progress p)) { /* ... */ }  // false instead of throwing
SavedNot saved
Public instance fields (mark one [NonSerialized] to skip it)Properties, static and readonly fields
int, long, float, double, bool, string, char, decimal; enums as numbersDictionary (written as null)
Arrays, List<T>, nested classes and structs, vectors and QuaternionEngine handles such as Entity (written as null)

Loading is forgiving: a field missing from the JSON keeps its initializer value and an unknown key is ignored, so saves from older versions of your game still load.

ScriptableObject data assets

Shared, designer-editable data (weapon stats, enemy types, level tables) belongs in a ScriptableObject:

[CreateAssetMenu(MenuName = "Combat/Weapon", FileName = "New Weapon")]
public class WeaponData : ScriptableObject
{
    public float Damage = 10f;
    public float Cooldown = 0.5f;
    public Prefab Projectile;
    public WeaponData Upgrade;            // assets can reference other assets

    protected override void OnEnable() { /* runs once the saved values are loaded */ }
}

public class Player : Entity
{
    public WeaponData Weapon;             // drop a WeaponData .ktxasset here
    public WeaponData[] Loadout;

    void OnCreate()
    {
        Log.Info(quot;{Weapon.Name}: {Weapon.Damage} damage");
        var sword = ScriptableObject.Load<WeaponData>("Data/Weapons/BossSword");   // path under Assets
        var temp  = ScriptableObject.CreateInstance<WeaponData>();                  // runtime only, not saved
    }
}

Create the asset in the Content Browser (right-click ▸ Create Scriptable Object) and edit it in the Scriptable Object panel.

RuleDetail
One instance per assetEvery field that references the same asset gets the same object, so it works as shared state across entities and scenes.
Type safetyA field only accepts assets of its class or a subclass.
Runtime changesLast for the Play session only; the asset file is not changed.
Builds.ktxasset files ship inside data.pak on every platform.