Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Prefabs, unique objects and 'extends MonoBehavior'

Discussion in 'Scripting' started by deLord, Oct 12, 2014.

  1. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    Hello guys

    I am very new to Unity but fortunately bring a lot of Java knowledge, thus making C# easy-mode for me. I also tried UDK before but having seen Unity in action by mself just for 20min, for me there is no question which framework to use ;)

    I understand: OOP, prefabs
    What I don't understand: You can create a GameManager class that is like the brain of the game. It can hold all the information that is necessary for the game. I created it by creating a new "empty" object in the editor and assigned the script to it. This script says that it extends from MonoBehavior. To me that means that a class extending it is not allowed to use constructors but should rather use Instantiate(). And every (?) object that is to be placed inside the editor must (?) extends MonoBehavior. But in my simple scenario how would one design this:
    My GameManager holds the information about the current arena (class Arena) the player is in. This arena consists of a 2D array of ArenaFields. Now an Arena itself can not be placed ingame, because only the arena fields are important. How is the connection between the GameManager, Arena and ArenaField and what are the inheritances?

    GameManager extends MonoBehavior and knows private Arena
    Arena
    extends ? and knows ArenaField[,] (but if it doesn't extend from MonoBehavior, should it instantiate ArenaFields by MonoBehavior.Instantiate()?)
    ArenaField extends ?

    Is this even the right way to go? Can someone explain me this simple chain of inheritance, how to instantiate (new or instantiate) and in general how to design the Arena class? Sorry if this question is noob but I didn't find anything on that yet.

    Additional info: Arena needs a constructor which defines the length and width of the arena. The arena will thus have a size of x*y ArenaFields. For each level (however I will need to define this) the arena can have a different x/y size
     
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    Not everything has to derive from MonoBehaviour. Only the class that are directly attached to a GameObject.

    If Arena and ArenaField are just data container, they can simply derive from nothing, and have their constructor work as usual.
     
  3. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Unity encourages component based development where each of your scripts extend the base class MonoBehaviour and can thus be attached to some game object.

    "Object.Instantiate" can be used to create a new instance of a game object from a prefab. Generally you should use "Awake" or "Start" to construct your object instead of vanilla C# constructors:
    Code (csharp):
    1. public class Arena : MonoBehaviour {
    2.     private void Awake() {
    3.         // Construct your arena here...
    4.     }
    5. }
    Components can be associated with one another using the inspector interface. Alternatively you can perform slower lookups (with Object.FindXyz) upon initializing your script using "Awake" or "Start".

    Private references must be marked with the "SerializeField" attribute if you intend to make this association using the inspector window:
    Code (csharp):
    1. [SerializeField] private Arena _someArena;
    I would suggest watching the following video:
    http://unity3d.com/learn/tutorials/modules/beginner/scripting/scripts-as-behaviour-components

    I hope that this helps :)
     
  4. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    But that is my very point: While ArenaField can be put into the scene, an arena is not needed.

    @numberkruncher: I watched all the videos already and feel like I understood it. Yet, in your description, I don't see how you can make the GameManager init new arenas of different sizes.

    What happens atm is that I get the following exception, dunno why though:
    NullReferenceException: A null value was found where an object instance was required.
    Arena.Start () (at Assets/scripts/Arena.cs:24)

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Arena : MonoBehaviour {
    5.  
    6.     public GameObject arenaFieldPrefab;
    7.  
    8.     public int dimX;
    9.     public int dimY;
    10.     private ArenaField[,] fields;
    11.  
    12.     public Arena(int dimX, int dimY){
    13.         this.dimX = dimX;
    14.         this.dimY = dimY;
    15.         fields = new ArenaField[dimX, dimY];
    16.     }
    17.  
    18.     // Use this for initialization
    19.     void Start () {
    20.         Debug.Log("Start aus Arena. Dims: "+ dimX +"/"+ dimY);
    21.         for(int x = 0 ; x < dimX ; x++){
    22.             for(int y = 0 ; y < dimY ; y++){
    23.                 Debug.Log("!");
    24.                 fields[x,y] = ((GameObject)Instantiate(arenaFieldPrefab, new Vector3(x,1,y), Quaternion.Euler(new Vector3()))).GetComponent<ArenaField>(); // no matter how I cast here or if I assign it or not. Plus there will be one field in the scene that lies somewhere around -1,/-1 ô.O
    25.                 Debug.Log ("Fields xy: " + fields[x,y]);
    26.             }
    27.         }
    28.     }
    29.  
    30.     // Update is called once per frame
    31.     void Update () {
    32.  
    33.     }
    34. }
     
  5. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    You can't use paramaterized constructors with MonoBehaviours so the following is not valid:
    Code (csharp):
    1.     public Arena(int dimX, int dimY){
    2.         this.dimX = dimX;
    3.         this.dimY = dimY;
    4.         fields = new ArenaField[dimX, dimY];
    5.     }
    Instead you would specify the dimensions using the inspector or an initialization method for which your "GameManager" could invoke:
    Code (csharp):
    1. public class Arena : MonoBehaviour {
    2.     // If you want to expose dimensions they should probably be read-only in this example.
    3.     public int DimX { get; private set; }
    4.     public int DimY { get; private set; }
    5.  
    6.     private ArenaField[,] fields;
    7.  
    8.     public void Initialize(int dimX, int dimY) {
    9.         DimX = dimX;
    10.         DimY = dimY;
    11.  
    12.         fields = new ArenaField[dimX, dimY];
    13.  
    14.         for (int x = 0; x < dimX; ++x) {
    15.             ...
    16.         }
    17.     }
    18. }
    19.  
    20. public class GameManager : MonoBehaviour {
    21.     private void Awake() {
    22.         var arenaGO = new GameObject("Arena");
    23.         var arena = arenaGO.AddComponent<Arena>();
    24.         arena.Initialize(10, 10);
    25.     }
    26. }
     
  6. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    You can just get rid of the constructor and simply put the line "fields =new ArenaField[dimX, dimY];" in top of Start().
     
  7. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    @numberkruncher: thanks. If that is the common case and how to handle it, I'd say it is not perfectly clean since you have to explicitly invoke another init() method although a constructor should do this job. I will try this approach if this is state-of-the-art/necessity in Unity

    @Fraconte: and where does dimX and dimY come from if I omit the constructor? Then I have the same as numberkruncher wrote (Initialize() the Arena object and set() the values afterwards)? Or is your approach different?

    Just trying to find out the proper way of doing things instead of regretting making noob mistakes later in the development :)

    And btw: how exactly must the Arena object then instantiate the ArenaField objects? Exactly as I did? And if Instantiate() returns an Object, then casting to ArenaField is not possible. How can I resolve this problem then?

    Code (CSharp):
    1.     public void Initialize(int dimX, int dimY) {
    2.         DimX = dimX;
    3.         DimY = dimY;
    4.      
    5.         fields = new ArenaField[dimX, dimY];
    6.      
    7.         for(int x = 0 ; x < dimX ; x++){
    8.             for(int y = 0 ; y < dimY ; y++){
    9.                 Instantiate(arenaFieldPrefab, new Vector3(x, 0, y), Quaternion.identity);
    10.                 var fieldGO = new GameObject("ArenaField");
    11.                 var field = fieldGO.AddComponent<ArenaField>();
    12.                 field.transform.position = new Vector3(x,1,y);
    13.                 fields[x,y] = field;
    14.                 Debug.Log ("Fields xy: " + fields[x,y]);
    15.             }
    16.         }
    17.     }
    But I cannot assign the instantiated OBJECT to my fields array. And somehow I have the impression that this code is way too complicated for what I want to achieve. Plus I didn't even tell each field its position (ArenaField has a Vector2 for that). What's too much here?

    What I found in an example game like Doodle Jump is the following code
    Code (CSharp):
    1. Transform plat = (Transform)Instantiate(platformPrefab, pos, Quaternion.identity);
    2. platforms.Add(plat);
    But why is the platformPrefab cast to a Transform?? I thought this component is only there for the position. I expected the Instantiate function to return an OBJECT. But seems it can be cast in many ways (e.g. to GameObject)? Can someone explain?
     
    Last edited: Oct 12, 2014
  8. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    You can set them in the editor (they are public variables).
     
  9. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    This should work. If you get a NullReferenceException then you probably miss an ArenaField script in the arenaFieldPrefab (I am not sure but I think it have to be at the top level too).
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Arena : MonoBehaviour {
    6.     public GameObject arenaFieldPrefab;
    7.     public int dimX;
    8.     public int dimY;
    9.     private ArenaField[,] fields;
    10.  
    11.     // Use this for initialization
    12.     void Start () {
    13.         fields = new ArenaField[dimX, dimY];
    14.  
    15.         Debug.Log("Start aus Arena. Dims: "+ dimX +"/"+ dimY);
    16.  
    17.         for(int x = 0 ; x < dimX ; x++){
    18.             for(int y = 0 ; y < dimY ; y++){
    19.                 Debug.Log("!");
    20.                 fields[x,y] = ((GameObject)Instantiate(arenaFieldPrefab, new Vector3(x,1,y), Quaternion.Euler(new Vector3()))).GetComponent<ArenaField>(); // no matter how I cast here or if I assign it or not. Plus there will be one field in the scene that lies somewhere around -1,/-1 ô.O
    21.                 Debug.Log ("Fields xy: " + fields[x,y]);
    22.             }
    23.         }
    24.     }
    25.     // Update is called once per frame
    26.     void Update () {
    27.     }
    28. }
     
  10. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    It really depends on what use you want to make of the object casted. Transform haves direct access to position, rotation and scale but to all the childrens too. And you can always get the gameObject or other components from it.
     
  11. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    So to me this all seems like it is more usualy to "develop" in the editor than inside scripts.

    And what about the code I wrote in the third post; is it optimal??
     
  12. Fraconte

    Fraconte

    Joined:
    Dec 6, 2013
    Posts:
    327
    In that script you Instantiate a prefab but you add the ArenaField script to a new empty GameObject. You can do something like fieldGO = Instantiate() and get rid of the next line if you want to add the ArenaField script to the prefab at runtime. But if you don't need that extra flexibility, just add the script to the prefab in the editor.