Forum rules - please read before posting.

Timeline Player Question

Hi, I use timeline alot in my project especially to set up shots (See below). I was wondering what is the best practice for doing this when I want the characters in the shot to be Player characters?

I understand that you can rebind the track to the player character at runtime, but how is the best way to preview these timelines in edit mode? Right now I just have a copy of the player characters in the scene do preview it, but I get error messages on the console when I enter playmode that you arnt supposed to have those prefabs in the scene, they need to be warped in. But if they arnt in the scene then I can't preview the timeline in edit mode. Thanks!

Comments

  • Using a Timeline-only character is a fair approach - it's the one used by the 3D Primer.

    You don't need to keep the Player prefab out of the scene file if it's inconvenient, though. Rather than spawning them in at runtime, you can place the Player in the scene file manually and use that same object within Timeline, avoiding the need to rebind.

    If you do want to use a copy of the Player character, and rebind to the correct one at runtime, you can convert your copy into an NPC so that AC doesn't complain about them at runtime. To do this, use the Player's cog button at the top-right and click "Convert to NPC".

  • Thank you for the answer Chris! One complication to that that I am running into is that AC keeps a record of where the Characters canonically 'Are' in the game correct? (Like which scene they were last in.
    Just placing the characters prefab into the current scene doesn't change that, As far as I understand?
    So if, during gameplay in this scene I want to switch to one of the characters for which I brought in their prefab, that doesn't currently work cause the game is looking for that character in a scene it last was 'Recorded' So I have to 'teleport in Inactive character', then swap to them, which causes a duplicate of the character in the scene (The Prefab I brought in for the timeline previews and the Prefab brought with the Teleport in Inactive Action'.

    Is there a way for AC to detect whatever character prefabs are in the scene upon running of the scene and then update the characters last known (Current scene Index I think is the term used) location to this scene and transition that currently existing character prefab into the Do Not Delete Scene where the rest of the player characters live?

    Or am I thinking about this all wrong?

  • To be clear: these other characters are Players listed in the Settings Manager, with "Player switching" enabled?

    If so, you'll need to let AC be in control over where each of the Players are. The Player: Teleport inactive Action is the way to bring a Player to the current scene and have AC record that change.

    The same trick of converting Timeline-only characters to NPCs should still work when Player-switching is enabled. But once you've converted to an NPC, they don't need to be "preview only" - they can be used in both Edit mode and runtime without needing to replace via bindings.

  • edited October 1

    'To be clear: these other characters are Players listed in the Settings Manager, with "Player switching" enabled?'
    Correct, just upgraded to AC 1.84

    "If so, you'll need to let AC be in control over where each of the Players are."
    If possible would I be able to get a bit more information on this aspect?

    This is the script I worked on to help AC to detect whatever character prefabs are in the scene upon running of the scene and then update the characters last known (Current scene Index) location to this scene. It seems to me working pretty good in the testing I did today, but I get the feeling I'm making things harder for myself down the line or something?

    '
    // AdoptScenePlayer.cs
    // Scene-placed Player adopter for Adventure Creator player-switching.
    //
    // Key points:
    // - Soft-seeds SaveSystem data for the prefab Player ID (scene + pos/rot) WITHOUT claiming the ID.
    // - Seeds very early (OnInitialiseScene) so early PlayerSwitch uses correct scene/pose.
    // - On spawn, optionally overrides the spawned prefab's pose to match scene instance
    // to avoid default PlayerStart placement if a switch happens right at scene start.
    // - Adopts transform/data from any spawned duplicate of the same ID.
    // - Never switches control during Teleport Inactive; only switches when AC actually sets this ID active.

    using System.Collections;
    using UnityEngine;

    #if !ACIgnore
    using AC;
    #endif

    [AddComponentMenu("BT/AC/Adopt Scene Player")]
    public class AdoptScenePlayer : MonoBehaviour
    {
    #if !ACIgnore

    [Header("Identity")]
    [Tooltip("AC Player ID for this character (Settings Manager → Players).")]
    public int playerID = -1;
    
    [Header("Timing")]
    [Tooltip("Run bootstrap on Awake; otherwise on Start.")]
    public bool runOnAwake = true;
    
    [Tooltip("Extra delay (seconds) before bootstrap. 0 = none.")]
    public float extraDelay = 0f;
    
    [Header("Behaviour")]
    [Tooltip("If true, keep this object disabled until AC actually switches to this Player ID.")]
    public bool keepDormantUntilAdopted = false;
    
    [Tooltip("Adopt transform/data on spawned duplicates without switching control (for Teleport Inactive / preloads).")]
    public bool autoPrepareOnSceneLoad = true;
    
    [Tooltip("Soft-seed SaveSystem data for this Player ID on scene init (scene index/name + pos/rot) without claiming the ID.")]
    public bool seedPlayerDataOnStart = true;
    
    [Header("Spawn control (to avoid default PlayerStart)")]
    [Tooltip("When AC spawns the prefab of this Player ID, immediately snap it to the scene instance's transform before adoption.")]
    public bool preferSceneTransformOnSpawn = true;
    
    [Tooltip("When adopting from a spawned duplicate, force the saved pose we load to this scene instance's transform.")]
    public bool preferSceneTransformOnAdopt = true;
    
    private Player self;
    
    // Subscribe as early as possible so we catch OnInitialiseScene
    private void OnEnable()
    {
        self = GetComponent<Player>();
    
        EventManager.OnInitialiseScene += OnInitialiseSceneHook;
        EventManager.OnPlayerSpawn     += OnAnyPlayerSpawn;
        EventManager.OnSetPlayer       += OnSetPlayer;
    }
    
    private void OnDisable()
    {
        EventManager.OnInitialiseScene -= OnInitialiseSceneHook;
        EventManager.OnPlayerSpawn     -= OnAnyPlayerSpawn;
        EventManager.OnSetPlayer       -= OnSetPlayer;
    }
    
    private void Awake()
    {
        if (keepDormantUntilAdopted && Application.isPlaying && KickStarter.player != self)
            gameObject.SetActive(false);
    
        if (runOnAwake) StartCoroutine(Bootstrap());
    }
    
    private void Start()
    {
        if (!runOnAwake) StartCoroutine(Bootstrap());
    }
    
    private IEnumerator Bootstrap()
    {
        if (!Application.isPlaying) yield break;
    
        // Wait for AC to be ready
        while (KickStarter.stateHandler == null ||
               KickStarter.eventManager == null ||
               KickStarter.saveSystem == null ||
               KickStarter.sceneChanger == null ||
               KickStarter.settingsManager == null)
        {
            yield return null;
        }
    
        if (extraDelay > 0f)
            yield return new WaitForSeconds(extraDelay);
    
        if (!EnsureSelfAndID(out int id))
            yield break;
    
        // If AC already thinks this ID is active, align/adopt
        if (KickStarter.player != null && SameID(KickStarter.player, id))
        {
            if (KickStarter.player != self)
            {
                // AC spawned another instance for this ID; adopt, "makeActive" will be handled in OnSetPlayer
                AdoptFromOther(KickStarter.player, makeActive: true);
            }
            else if (!gameObject.activeSelf)
            {
                gameObject.SetActive(true);
            }
        }
    }
    

    `

  • ` // --- AC early scene init hook: seed BEFORE OnStart ActionLists run ---
    private void OnInitialiseSceneHook()
    {
    if (!Application.isPlaying) return;

        if (!EnsureSelfAndID(out int id)) return;
    
        if (seedPlayerDataOnStart)
        {
            SoftSeedPlayerDataForID(id, self);
        }
    }
    
    /// <summary>
    /// Fired whenever AC spawns a Player instance (switching, Teleport Inactive, etc).
    /// We can override the spawn pose and then adopt. We DO NOT switch control here.
    /// </summary>
    private void OnAnyPlayerSpawn(Player spawned)
    {
        if (!Application.isPlaying || spawned == null) return;
        if (!EnsureSelfAndID(out int id)) return;
    
        if (!SameID(spawned, id)) return;
    
        // 1) If desired, force the spawned prefab to our scene instance pose *immediately*,
        //    so any logic between spawn and adopt uses the correct location.
        if (preferSceneTransformOnSpawn)
        {
            spawned.transform.SetPositionAndRotation(self.transform.position, self.transform.rotation);
    
            // Keep SaveSystem consistent too (helps if anything reads PlayerData immediately)
            if (seedPlayerDataOnStart)
                SoftSeedPlayerDataForID(id, self);
        }
    
        // 2) Adopt (no control switch here)
        if (spawned != self)
        {
            AdoptFromOther(spawned, makeActive: false);
        }
        else
        {
            EnsureAwakeAsActiveIfNeeded(spawned);
        }
    }
    
    /// <summary>
    /// Fired when AC sets a new active player. ONLY here do we switch control.
    /// </summary>
    private void OnSetPlayer(Player newActive)
    {
        if (!Application.isPlaying || newActive == null) return;
        if (!EnsureSelfAndID(out int id)) return;
    
        if (SameID(newActive, id) && newActive != self)
        {
            AdoptFromOther(newActive, makeActive: true);
        }
        else if (SameID(newActive, id) && newActive == self)
        {
            if (!gameObject.activeSelf) gameObject.SetActive(true);
        }
    }
    
    private void AdoptFromOther(Player other, bool makeActive)
    {
        if (!gameObject.activeSelf)
            gameObject.SetActive(true);
    
        // Copy runtime data; prefer our scene transform for pose if requested
        bool loaded = false;
        try
        {
            var pd = new PlayerData();
            other.SaveData(pd);
    
            if (preferSceneTransformOnAdopt && self)
            {
                Vector3 p = self.transform.position;
                pd.playerLocX = p.x;
                pd.playerLocY = p.y;
                pd.playerLocZ = p.z;
                pd.playerRotY = self.TransformRotation.eulerAngles.y;
            }
    
            // Player.LoadData is IEnumerator in recent AC; run it async
            StartCoroutine(self.LoadData(pd));
            loaded = true;
        }
        catch
        {
            // API may differ; we'll at least sync transform
        }
    
        // Always snap transform (covers both paths / ensures exact match)
        if (self && other)
            self.transform.SetPositionAndRotation(self.transform.position, self.transform.rotation);
    
        // Only switch control if AC truly changed active player to this ID
        if (makeActive)
        {
            KickStarter.player = self;
        }
    
        // Remove duplicate safely
        if (other && other.gameObject != gameObject)
        {
            other.gameObject.SetActive(false);
            Object.Destroy(other.gameObject);
        }
    }
    
    private void EnsureAwakeAsActiveIfNeeded(Player p)
    {
        if (keepDormantUntilAdopted && p == self && !gameObject.activeSelf)
            gameObject.SetActive(true);
    }
    
    private bool EnsureSelfAndID(out int id)
    {
        id = -1;
    
        if (self == null)
            self = GetComponent<Player>();
    
        if (self == null)
        {
            Debug.LogWarning($"[AdoptScenePlayer] {name}: No AC.Player on this object.");
            return false;
        }
    
        if (playerID < 0)
        {
            Debug.LogWarning($"[AdoptScenePlayer] {name}: Set 'playerID' in the inspector to the prefab Player ID this character represents.");
            return false;
        }
    
        id = playerID;
        return true;
    }
    
    private static bool SameID(Player p, int id)
    {
        try { return p != null && p.ID == id; }
        catch { return false; }
    }
    
    /// <summary>
    /// Softly seed SaveSystem's PlayerData for the prefab Player ID:
    /// - Set current scene index/name
    /// - Set position/rotation (Y)
    /// No call to self.ID, so the scene instance remains 'local' and won't be swept.
    /// </summary>
    private void SoftSeedPlayerDataForID(int id, Player source)
    {
        var ss = KickStarter.saveSystem;
        var sm = KickStarter.settingsManager;
        if (ss == null || sm == null || source == null) return;
    
        PlayerData pd = ss.GetPlayerData(id);
        if (pd == null) return; // no prefab defined for this ID
    
        // Scene reference
        switch (sm.referenceScenesInSave)
        {
            case ChooseSceneBy.Name:
                pd.currentSceneName = SceneChanger.CurrentSceneName;
                break;
            case ChooseSceneBy.Number:
            default:
                pd.currentScene = SceneChanger.CurrentSceneIndex;
                break;
        }
    
        // Pose from our scene instance
        Vector3 p = source.transform.position;
        pd.playerLocX = p.x;
        pd.playerLocY = p.y;
        pd.playerLocZ = p.z;
        pd.playerRotY = source.TransformRotation.eulerAngles.y;
    
        // Clean up invalid scene refs
        if (sm.referenceScenesInSave == ChooseSceneBy.Number && pd.currentScene < 0)
            pd.currentScene = SceneChanger.CurrentSceneIndex;
        if (sm.referenceScenesInSave == ChooseSceneBy.Name && string.IsNullOrEmpty(pd.currentSceneName))
            pd.currentSceneName = SceneChanger.CurrentSceneName;
    }
    

    endif

    }`

  • "If so, you'll need to let AC be in control over where each of the Players are." If possible would I be able to get a bit more information on this aspect?

    When using Player-switching, AC will keep tabs on where each Player is throughout the game - taking care of spawning / removal duties based on what scene is open, and which scene they're supposed to be in.

    This is based on its internal data-keeping, so the "regular" way to move characters around is to use Player: Teleport inactive which allows AC to make changes to this data at the same time.

    If you want to add/remove Players yourself, you'll run in to trouble unless you use a complex custom script like the one above to update AC's data set to keep things in sync. If you hadn't written one already, I'd recommend against it. But the main thing is to update the SaveSystem's PlayerData, which you are. I haven't tested it myself but it does look thorough.

  • edited October 1

    Ok, great to know. Is there a way to check where AC thinks all the characters are at moment in the game? Like which scenes? I've been using this script, but it only tracks the characters in the current scene
    `// PlayerLocationOverlay.cs
    // Scene-attached, non-interactive overlay that shows all AC Player instances:
    // • Which scene they're in
    // • Their world position
    // • Which one is the active Player
    //
    // Attach this to any GameObject in scenes where you want the overlay available.
    // Toggle with F10 (configurable). Updates ~1/sec by default.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.SceneManagement;

    if AC_PRESENT || true

    using AC; // Adventure Creator

    endif

    [AddComponentMenu("BT/Debug/Player Location Overlay")]
    [DefaultExecutionOrder(10001)]
    public class PlayerLocationOverlay : MonoBehaviour
    {
    [Header("Toggle")]
    public KeyCode toggleKey = KeyCode.F10;

    [Header("Refresh")]
    [Tooltip("Seconds between rescans for new/removed Players.")]
    [Min(0.1f)] public float rescanInterval = 1.0f;
    [Tooltip("Seconds between position/state refreshes.")]
    [Min(0.1f)] public float refreshInterval = 1.0f;
    
    [Header("Filters")]
    [Tooltip("Only show Players in the currently-active scene.")]
    public bool onlyActiveScene = false;
    
    [Header("Style")]
    [Range(10, 28)] public int fontSize = 14;
    public Color headerColor  = new Color(0.9f, 0.9f, 1f, 1f);
    public Color activeColor  = new Color(0.50f, 1f, 0.60f, 1f);
    public Color normalColor  = Color.white;
    public Color disabledColor = new Color(1f, 0.65f, 0.65f, 1f);
    public int   maxLines = 100;
    
    [Header("Layout (Lower-left by default)")]
    public Vector2 anchorMin = new Vector2(0f, 0f);
    public Vector2 anchorMax = new Vector2(0.35f, 0.35f);
    public Vector2 offset    = new Vector2(8f, 8f);
    
    private class PlayerInfo
    {
        public Player acPlayer;      // AC Player component (if available)
        public GameObject go;        // GO reference (fallback)
        public string name;
        public string sceneName;
        public bool isActivePlayer;
        public Vector3 position;
        public bool isEnabled;
    }
    
    private readonly List<PlayerInfo> _items = new List<PlayerInfo>();
    private float _scanTimer;
    private float _refreshTimer;
    
    // UI
    private Canvas _canvas;
    private Text _text;
    
    void Awake()
    {
        // Keep just one per scene
        foreach (var other in FindObjectsOfType<PlayerLocationOverlay>())
        {
            if (other != this && other.gameObject.scene == gameObject.scene)
            {
                Debug.LogWarning("[PlayerLocationOverlay] Duplicate in scene; destroying one.");
                DestroyImmediate(gameObject);
                return;
            }
        }
    
        BuildUI();
        Rescan();
    
        UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
        UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
    }
    
    void OnDestroy()
    {
        UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
        UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
    }
    
    void OnSceneLoaded(UnityEngine.SceneManagement.Scene s, LoadSceneMode m) => Rescan();
    void OnSceneUnloaded(UnityEngine.SceneManagement.Scene s) => Rescan();
    
    void Update()
    {
        if (Input.GetKeyDown(toggleKey) && _canvas) _canvas.enabled = !_canvas.enabled;
    
        _scanTimer += Time.unscaledDeltaTime;
        if (_scanTimer >= rescanInterval)
        {
            _scanTimer = 0f;
            Rescan();
        }
    
        _refreshTimer += Time.unscaledDeltaTime;
        if (_refreshTimer >= refreshInterval)
        {
            _refreshTimer = 0f;
            RefreshStates();
            RenderText();
        }
    }
    

    `

  • ` // ---------- Build UI ----------
    private void BuildUI()
    {
    _canvas = gameObject.AddComponent();
    _canvas.renderMode = RenderMode.ScreenSpaceOverlay;
    _canvas.sortingOrder = short.MaxValue;

        // Click-through
        var existingRaycaster = GetComponent<GraphicRaycaster>();
        if (existingRaycaster) Destroy(existingRaycaster);
        var scaler = gameObject.AddComponent<CanvasScaler>();
        scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        scaler.referenceResolution = new Vector2(1920, 1080);
        scaler.matchWidthOrHeight = 1f;
        var cg = gameObject.AddComponent<CanvasGroup>();
        cg.interactable = false;
        cg.blocksRaycasts = false;
    
        // Panel
        var panelGO = new GameObject("TextPanel", typeof(RectTransform));
        panelGO.transform.SetParent(_canvas.transform, false);
        var rt = (RectTransform)panelGO.transform;
        rt.anchorMin = anchorMin;
        rt.anchorMax = anchorMax;
        rt.pivot = new Vector2(0f, 0f);
        rt.anchoredPosition = offset;
    
        // Text
        var textGO = new GameObject("Text", typeof(RectTransform));
        textGO.transform.SetParent(panelGO.transform, false);
        var tr = (RectTransform)textGO.transform;
        tr.anchorMin = new Vector2(0f, 0f);
        tr.anchorMax = new Vector2(1f, 1f);
        tr.offsetMin = new Vector2(6f, 6f);
        tr.offsetMax = new Vector2(-6f, -6f);
    
        _text = textGO.AddComponent<Text>();
        _text.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
        _text.fontSize = fontSize;
        _text.alignment = TextAnchor.UpperLeft;
        _text.horizontalOverflow = HorizontalWrapMode.Wrap;
        _text.verticalOverflow = VerticalWrapMode.Overflow; // ✅ correct enum
        _text.raycastTarget = false;
        _text.supportRichText = true;
        _text.text = "Player Location Overlay (initializing...)";
    }
    
    // ---------- Data ----------
    private void Rescan()
    {
        var found = FindAllPlayers();
        var set = new HashSet<GameObject>(found.Select(f => f.go));
    
        _items.RemoveAll(p => p.go == null || !set.Contains(p.go));
    
        foreach (var p in found)
        {
            if (_items.Any(x => x.go == p.go)) continue;
            _items.Add(p);
        }
    }
    
    private void RefreshStates()
    {
        Player active = null;
        try { active = KickStarter.player; } catch { }
    
        for (int i = 0; i < _items.Count; i++)
        {
            var info = _items[i];
            if (!info.go) continue;
    
            info.position = info.go.transform.position;
            info.sceneName = SafeSceneName(info.go);
            info.isEnabled = info.go.activeInHierarchy && info.go.activeSelf;
            info.isActivePlayer = (active && info.acPlayer && info.acPlayer == active);
            _items[i] = info;
        }
    }
    
    private List<PlayerInfo> FindAllPlayers()
    {
        var list = new List<PlayerInfo>();
    
        // Preferred path via AC SettingsManager (handles player switching & scene instances)
        try
        {
            if (KickStarter.settingsManager != null)
            {
                var acPlayers = KickStarter.settingsManager.GetAllPlayerInstances(); // AC API
                foreach (var p in acPlayers)
                {
                    if (!p) continue;
                    if (!IsSceneLoadedObject(p.gameObject)) continue;
                    list.Add(new PlayerInfo
                    {
                        acPlayer = p,
                        go = p.gameObject,
                        name = p.gameObject.name,
                        sceneName = SafeSceneName(p.gameObject),
                        position = p.transform.position,
                        isEnabled = p.gameObject.activeInHierarchy,
                        isActivePlayer = false, // filled in RefreshStates
                    });
                }
            }
        }
        catch { /* fall through to brute-force below as needed */ }
    
        // Brute-force fallback (any Player components in loaded scenes)
        if (list.Count == 0)
        {
            var all = Resources.FindObjectsOfTypeAll<Player>();
            foreach (var p in all)
            {
                if (!p) continue;
                var go = p.gameObject;
                if (!IsSceneLoadedObject(go)) continue;
    
                list.Add(new PlayerInfo
                {
                    acPlayer = p,
                    go = go,
                    name = go.name,
                    sceneName = SafeSceneName(go),
                    position = go.transform.position,
                    isEnabled = go.activeInHierarchy,
                    isActivePlayer = false,
                });
            }
        }
    
        // As a final safety net, catch any GameObjects tagged "Player" (non-AC)
        if (list.Count == 0)
        {
            foreach (var go in GameObject.FindGameObjectsWithTag("Player"))
            {
                if (!IsSceneLoadedObject(go)) continue;
                list.Add(new PlayerInfo
                {
                    acPlayer = go.GetComponent<Player>(),
                    go = go,
                    name = go.name,
                    sceneName = SafeSceneName(go),
                    position = go.transform.position,
                    isEnabled = go.activeInHierarchy,
                    isActivePlayer = false,
                });
            }
        }
    
        return list;
    }
    
    // ---------- Render ----------
    private void RenderText()
    {
        if (_text == null) return;
    
        var sb = new System.Text.StringBuilder(512);
        string scenes = SceneList();
        AppendLine(sb, $"<b><color=#{Hex(headerColor)}>Players Monitor</color></b>  |  Scenes: {scenes}  |  Found: {_items.Count}");
    
        IEnumerable<PlayerInfo> display = _items;
    
        if (onlyActiveScene)
        {
            var active = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
            display = display.Where(p => p.sceneName == active);
        }
    
        // Sort: active player first, then by scene, then by name
        display = display
            .OrderByDescending(p => p.isActivePlayer)
            .ThenBy(p => p.sceneName, StringComparer.OrdinalIgnoreCase)
            .ThenBy(p => p.name, StringComparer.OrdinalIgnoreCase);
    
        int lines = 0;
        foreach (var p in display)
        {
            if (!p.go) continue;
            if (lines >= maxLines) { AppendLine(sb, "..."); break; }
    
            var col = p.isActivePlayer ? activeColor : (p.isEnabled ? normalColor : disabledColor);
            string dot = $"<color=#{Hex(col)}>●</color>";
            string activeTag = p.isActivePlayer ? " <b>[ACTIVE]</b>" : "";
            AppendLine(sb, $"{dot} <b>{p.name}</b>{activeTag}  <i>[{p.sceneName}]</i>");
            AppendLine(sb, $"    Pos: {p.position.x:F2}, {p.position.y:F2}, {p.position.z:F2}");
    
            lines += 2;
        }
    
        _text.text = sb.ToString();
    }
    

    `

  • ` // ---------- Helpers ----------
    private static bool IsSceneLoadedObject(GameObject go)
    {
    var s = go.scene;
    if (!s.IsValid() || !s.isLoaded) return false;
    var hf = go.hideFlags;
    if ((hf & HideFlags.HideAndDontSave) != 0) return false;
    return true;
    }

    private static string SafeSceneName(GameObject go)
    {
        var s = go.scene;
        return (s.IsValid() && s.isLoaded) ? s.name : "(no scene)";
    }
    
    private string SceneList()
    {
        int n = UnityEngine.SceneManagement.SceneManager.sceneCount;
        var names = new List<string>(n);
        for (int i = 0; i < n; i++)
        {
            var s = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i);
            if (s.isLoaded) names.Add(s.name);
        }
        return string.Join(", ", names);
    }
    
    private static string Hex(Color c)
    {
        Color32 c32 = c;
        return $"{c32.r:X2}{c32.g:X2}{c32.b:X2}{c32.a:X2}";
    }
    private static void AppendLine(System.Text.StringBuilder sb, string line) => sb.Append(line).Append('\n');
    

    }`

  • GetAllPlayerInstances will only return a List of Players found within the current scene.

    For a given Player ID, you can use GetPlayerSceneIndex / GetPlayerSceneName to get AC's record of their current location.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.