Search Unity

Instantiate scriptableobject from script

Discussion in 'Scripting' started by Kluckmuck, Nov 29, 2019.

  1. Kluckmuck

    Kluckmuck

    Joined:
    Nov 21, 2019
    Posts:
    5
    Hi,

    I'm a bit new to Unity, and I'm struggling to "create" a ScriptableObject at runtime. I want to create a gameobject, then assign that different values from a scriptableObject. I've watched what feels like a endless amount of youtube videos without getting a hang of this.

    My code is as follows:

    Code (CSharp):
    1. [CreateAssetMenu(fileName = "New Unit", menuName = "Unit")]
    2. public class Unit : ScriptableObject {
    3.  
    4.     public new string name;
    5.     public int maxHP;
    6.     public Sprite artwork;
    7.  
    8. }
    Code (CSharp):
    1. public class Enemy : MonoBehaviour
    2. {
    3.     public Unit enemy;
    4.     public Text nameText;
    5.     public Text health;
    6.     public Image image;
    7.  
    8.     // Start is called before the first frame update
    9.     void Start()
    10.     {
    11.  
    12.     }
    13.  
    14.     public void LoadEnemy()
    15.     {
    16.         health.text = enemy.maxHP.ToString();
    17.         nameText.text = enemy.name;
    18.         image.sprite = enemy.artwork;
    19.     }
    20. }
    21.  
    Code (CSharp):
    1. public class CombatManager : MonoBehaviour
    2. {
    3.     public Unit enemyAsset;
    4.  
    5.  
    6.     //SetupScene initializes our level and calls the previous functions to lay out the game board
    7.     public void SetupScene()
    8.     {
    9.         GameObject enemy = new GameObject("Enemy");
    10.         enemy.AddComponent<Enemy>();
    11.        
    12.         enemy.GetComponent<Enemy>().enemy.maxHP = enemyAsset.maxHP;
    13.         enemy.GetComponent<Enemy>().enemy.artwork = enemyAsset.artwork;
    14.         enemy.GetComponent<Enemy>().enemy.name = enemyAsset.name;
    15.     }
    16. }
    17.  
    Any help is appreciated!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    The main point of scriptable objects is to PRE define data as loadable/referenceable objects on disk.

    Basically they are a class that has special wiring to "connect" public fields in the class to specific chunks of data in a single instance of the ScriptableObject on disk, and also lets you change those values in the Inspector. The wiring is "live" so even if your game is running, if you hand-edit a ScriptableObject, the on-disk asset changes, and vice-versa.

    The same goes for if your code changes the on-disk object: it is one global object and will change.

    That said, you can Instantiate() a ScriptableObject, which basically makes a copy of it, but does NOT write it to disk.

    Code (csharp):
    1. var UnitCopy = Instantiate<Unit>( enemyAsset);
    If you want to write it to disk you need to do it at Editor time only (not runtime), and there is google-able reference material for doing that. Otherwise the copy will just disappear as soon as it is eligible for garbage collection or the app terminates.
     
    Kluckmuck likes this.
  3. Kluckmuck

    Kluckmuck

    Joined:
    Nov 21, 2019
    Posts:
    5
    So how can I assign these values to my Enemy class?
    What I would like to do is to pick a random ScriptableObject out of three, then assign one of them to a GameObject. Right now the Enemy will spawn as the Unit added to the prefab.



    Code (CSharp):
    1. public class CombatManager : MonoBehaviour
    2. {
    3.     public GameObject playerPrefab;
    4.     public GameObject enemyPrefab;
    5.  
    6.     public Unit unit;
    7.     public Transform enemySpawn;
    8.  
    9.     Enemy enemyAsset;
    10.     Player playerUnit;
    11.  
    12.     void Start()
    13.     {
    14.         SetupScene();
    15.     }
    16.     public void SetupScene()
    17.     {
    18.         GameObject playerGO = Instantiate(playerPrefab);
    19.         playerUnit = playerGO.GetComponent<Player>();
    20.  
    21.         GameObject enemyGO = Instantiate(enemyPrefab, enemySpawn);
    22.         enemyAsset = enemyGO.GetComponent<Enemy>();
    23.  
    24.     }
    25. }
    upload_2019-11-30_1-57-43.png

    If I want my prefab to change to the Blastoise Unit, how can I do that?

    Thanks for the help!
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    I would make an array of Units in the CombatManager, instead of just one:

    Code (csharp):
    1. public Unit[] PossibleUnits;
    Then fill it out in the inspector with all possible ones.

    In the manager, you would choose randomly from that list and supply it to the prefab you make, generally by Instantiating it and then injecting the supplied unit. You can save the prefab off with NO Unit in there to make sure it doesn't get used before it is defined (i.e., check it at runtime so you know the new Unit has been injected by the CombatManager).
     
    Kluckmuck likes this.
  5. Kluckmuck

    Kluckmuck

    Joined:
    Nov 21, 2019
    Posts:
    5
    How do I supply it to the prefab though?
    The one I supply to CombatManager is not used at this time.
    Instead I am using the one supplied to the prefab. Below is my enemy prefab.

    upload_2019-11-30_9-46-58.png

    I would be able to supply the "Enemy"field in Enemy with a Unit, instead of the one predefined.
     
  6. Kluckmuck

    Kluckmuck

    Joined:
    Nov 21, 2019
    Posts:
    5
    Found what I was looking for!
    Code (CSharp):
    1. enemyAsset.enemy = unit;
    2.  
    Thanks a lot for the help! :)
     
    Kurt-Dekker likes this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    I kinda left this open-ended because it is highly dependent on what you're most comfortable with.

    And it looks like you came up with an excellent solution!
     
  8. Kluckmuck

    Kluckmuck

    Joined:
    Nov 21, 2019
    Posts:
    5
    One thing I noticed was that I was ending up with two gameobjects in unity when using Instantiate, like this:
    Code (CSharp):
    1. GameObject playerGO = Instantiate(playerPrefab, playerSpawn);
    2.         playerAsset = playerGO.GetComponent<Player>();
    But without the lines of code above, I end up with zero copies. Is this the preferred way? Or should I "spawn" my gameobjects in another way?