Search Unity

Bug Singleton for multiple instances of a prefab

Discussion in 'Scripting' started by Hydrolios, Nov 30, 2022.

  1. Hydrolios

    Hydrolios

    Joined:
    Dec 19, 2021
    Posts:
    6
    Hello, I am using a prefab for an NPC gameobject used for encounters so I could have multiple of them in one scene. They all share the same encounter script, I used the singleton code below so I can refer to the NPC which triggered the battle scene so I can hide the gameobject if the player was victorious and returns to the scene.

    Code (CSharp):
    1. private void Awake() // for making this object a singleton
    2.     {
    3.         if (instance == null)
    4.         {
    5.             instance = this;
    6.             DontDestroyOnLoad(gameObject);
    7.         }
    8.         else if (instance != this)
    9.             Destroy(gameObject);
    10.     }
    11.   public void Start()
    12.     {
    13.         npcBattling = this.gameObject;
    14.         triggered.SetActive(false); //notification above NPC to display if they have been triggered by player entering line of sight
    15.     }
    16.  
    The problem I'm facing is that upon loading the scene with the NPCs in it, the duplicates are removed since they're all using the same script as they are from the same prefab. I'm not sure what the proper way of overcoming this is, as my battle manager is referring to the script that the NPC gameobject is using, to hide it after battle which is working fine.

    Code (CSharp):
    1. Encounter.npcBattling.SetActive(false);
    How should I go about distinguishing each one?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    With Unity it is trivial to make "singletons" that either live forever, live for a scene, or even live for a shorter amount of time, like an encounter.

    From your description, it sounds like you want something more like an encounter manager, something that ONLY knows about the things involved in the fight, nothing else.

    This is always the pattern I use for things that live a bit and perhaps manage other things.

    I would NEVER EVER drop such an item in a scene or prefab because it solves absolutely nothing. Unfortunately it is a pattern blindly applied by a lot of tutorial makers.

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , and only while it exists.
     
  3. SomeVVhIteGuy

    SomeVVhIteGuy

    Joined:
    Mar 31, 2018
    Posts:
    162
    A singleton is always single. Your npc's should not use a singleton method at all.

    an EncounterManager script might want to use one. say:
    -npc triggers encounter somehow (sees you walking infront of it like in pokemon
    -npc calls method in EncounterManager via EncounterManager.Instance.startEncounter(this)
    -EncounterManager adjusts what it needs to based on who triggered it. looking something like


    Code (CSharp):
    1. public void startEncounter(NPCClass _WasTrigger)
    2. {
    3.         //change spite on encounter screen to match _WasTrigger
    4.        //set hp bars and such based on _WasTrigger
    5.        //activate any entrance animations
    6.       //enable player controls for encounter
    7. }
     
  4. Hydrolios

    Hydrolios

    Joined:
    Dec 19, 2021
    Posts:
    6


    I tried to use your Singleton with a Prefab code, but I don't have a Resources folder which I use...
    https://imgur.com/a/TvYn5FE
    all my stuff is in a folder called Assets, when I tried to use the code, it runs fine but doesn't do anything.
    The prefab I'm trying to access is under the path "Assets/Prefabs/Assets/RBDGuard_L.prefab"

    My project has been an accumulation of a lot of tutorials I've come across for specific things so I'm not even sure if I set it up correctly as a Resources folder is found under "TextMesh Pro" folder
    https://imgur.com/a/Jg23YZk
     
  5. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    I haven't looked at his design, but you can add your own Resources folder under assets and put whatever you need in there if that is what is required.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    If you want to use Resources.Load<T>() you need a Resources folder.

    See if you have some spare letters lying around your desk and make a folder called Resources. If you run out of letters, I'll give you some extras to use here:
    Resources


    In seriousness, do stop by the documentation for Resources.Load<T>() to make sure you are using it correctly. It's the simplest system possible for free-form future content delivery, but it does still have specific requirements.
     
  7. Hydrolios

    Hydrolios

    Joined:
    Dec 19, 2021
    Posts:
    6
    I've made the folder and believe I set the path correctly, but I don't think the code is being ran, I've attached the snippet of code you provided to an NPCManager I made and its in the Scene, when I run it, it doesn't instantiate the prefab I set the path for. I placed Debug.Log lines through the snippets and it seems like it's never ran.

    is NPCManager not supposed to be in the scene or something?

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class NPCManager : MonoBehaviour
    6. {
    7.     public GameObject npcprefab;
    8.     public GameObject clonenpc;
    9.     public static GameObject battling;
    10.     public Vector2 npcpos;
    11.     private static NPCManager _Instance;
    12.     public static NPCManager Instance
    13.     {
    14.         get
    15.         {
    16.             Debug.Log("hello1");
    17.             if (!_Instance)
    18.             {
    19.                 Debug.Log("hello2");
    20.                 // NOTE: read docs to see directory requirements for Resources.Load!
    21.                 var prefab = Resources.Load<GameObject>("RBDGuard_L");
    22.                 // create the prefab in your scene
    23.                 var inScene = Instantiate(prefab);
    24.                 // try find the instance inside the prefab
    25.                 _Instance = inScene.GetComponentInChildren<NPCManager>();
    26.                 // guess there isn't one, add one
    27.                 if (!_Instance) _Instance = inScene.AddComponent<NPCManager>();
    28.                 // mark root as DontDestroyOnLoad();
    29.                 DontDestroyOnLoad(_Instance.transform.root.gameObject);
    30.             }
    31.             return _Instance;
    32.         }
    33.     }
    34.  
    35.     private void Start()
    36.     {
    37.         Debug.Log("hello3");
    38.  
    39.         clonenpc = Instantiate(npcprefab);
    40.         clonenpc.transform.position = npcpos;
    41.         battling = clonenpc;
    42.  
    43.  
    44.  
    45.     }
    46.  
    47. }
    The code I added in Start is what I'm trying to do with it, when I start the scene, I only see "hello3" in the console.

    To re-iterate what my original intent was, I want to have an NPC that gets triggered when the player walks in their line of sight like in Pokemon, and transitions to a Battle scene, after the battle scene, I want to go back to the previous scene but remove the NPC that initially triggered it if the player won.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5.  
    6. public class Encounter : MonoBehaviour
    7. {
    8.     [SerializeField] Dialogue dialogue;
    9.  
    10.     public GameObject player;
    11.     public GameObject triggered;
    12.     public string sceneToLoad;
    13.     public Player playerRef;
    14.  
    15.     public void Start()
    16.     {
    17.         triggered.SetActive(false); //notification above NPC to display if they have been triggered by player entering line of sight
    18.     }
    19.     public void OnTriggerEnter2D(Collider2D other)
    20.     {
    21.         if (other.CompareTag("Player") && !other.isTrigger)
    22.         {
    23.             triggered.SetActive(true);
    24.             StartCoroutine(DialogueManager.Instance.ShowDialogueV3(dialogue, sceneToLoad, true));
    25.          
    26.         }
    27.     }
    28. }
    with my BattleManager having the line
    Code (CSharp):
    1. NPCManager.battling.SetActive(false);
    in the WIN game state.

    EDIT: To add onto this, I am trying to use a prefab for the Guard asset I have because I want to have multiple encounters with the same setup, just in different locations throughout the dungeon I have. All the guards in a level are the same and each room may have different layouts with 1-5 guards in it. I previously had something working where it would hide the NPC after winning battle, but it ended up hiding every NPC in the scene whereas I wanted it to hide just the 1 that was battled
     
    Last edited: Dec 2, 2022
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    Your NPC manager appears to be loading something called
    RBDGuard_L
    ...

    Is
    RBDGuard_L
    the NPC manager or is that an NPC who needs the NPC manager's service?
     
  9. Hydrolios

    Hydrolios

    Joined:
    Dec 19, 2021
    Posts:
    6
    RBDGuard_L is the prefab I have which has the Encounter script, so the NPC who needs the NPC Manager to instantiate it

    Once Instantiated, when the Player triggers the Encounter, a Battle scene starts, when the player wins, under Battlestate = win, I want to hide the RBDGuard_L in the original scene where it triggered the Encounter.

    I have RBDGuard_L as a prefab as I wish to reuse it, instead of having a lot of unique guards.
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    That's weird... if it's a singleton there is exactly ONE of them, NEVER any more. That's what a singleton means.

    Usually you have an NPC Controller, many of them, each instance responsible for running a single NPC.

    Then you (might) have an NPC Manager that manages all the NPCs as a whole.

    Often to keep things simpler, the NPC Manager might just be a way of finding NPCs near enough to you to be involved in battle.

    That NPC manager would say "NPCs 1, 4 ,17 and 42, you are having an encounter with the player."

    Now those NPCs are placed under control of an Encounter script, which might destroy them, or destroy the player, or whatever.

    There's so many examples of turn based combat stuff online, it's such a well-traveled area, I won't bother retyping any more stuff here. This is kinda how to do a Golden Sun / Final Fantasy / Advance Wars kinda game.
     
  11. Hydrolios

    Hydrolios

    Joined:
    Dec 19, 2021
    Posts:
    6
    Yea, I've been trying to scour the internet trying to figure out how to do what I want to do/what's the proper way to do what I want to do, but after what seems like endless searching, I came here haha. I have pretty much all the actual battling okay, just trying to figure out the after battle stuff that I want to implement

    Well thanks for trying to help, still learned a thing or two, appreciate it!
     
    Kurt-Dekker likes this.
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,727
    Also, don't underestimate how helpful it can be to grab a piece of paper and scribble circles on it:

    - enemy1
    - enemy2
    - enemy3
    - player

    NPC manager

    and....

    ENCOUTER!

    draw lines for enemy1 and enemy3 and player into encounter... BATTLE!

    RESULTS!

    back to NPC manager, etc.

    I have notebooks full of scribbles like this, and it's always helpful while solving the problem at hand if you have some scribbles nearby. I just scribbled a whole page of notes today about a statistics reporter I'm adding to one of my old games, just to keep all the parts straight in my head.
     
    Hydrolios likes this.