Robot Repair in C# or clear example of class design while using MonoBehaviour

I am working through Ryan Creighton’s “Unity3D Game Development By Example” and I’m stuck on the “Robot Repair” example for chapter 5. I’ve been translating the code to C# rather well up to a point. The sample requires that the Card class inherit System.Object and then later this class be attached to the GameScreen. However, you can’t attach scripts that do not inherit MonoBehaviour. There are several stipulations involving “naming the file the same as the class name” and essentially, never using constructors when writing in C#. After quite a bit of wrestling, I’ve managed to get it to run by using 2 files (although Im not sure this is the ideal approach) but I’m still not seeing the game screen. In fact, I don’t think OnGUI is being called. You’ll see in “Card.cs” I do use a constructor but that’s how I figured out how to assign the “img” variable. Otherwise, I WAS using “Awake()” as documentation suggests but then the values were not assigned. I’d like a clear idea of how to use traditional OOP while still utilizing MonoBehavior if that is possible. Thanks. Here is my code:

GameScript.cs

using UnityEngine;
using System.Collections;

public class GameScript: MonoBehaviour {
	int rows=4;
	int cols=4;
	int totalCards=16;
	int matchesNeededToWin=8;
	int matchesMade=0;
	int cardH=100;
	int cardW=100;
	Card[] aCards;  //all cards
	Card[,] aGrid;  //keeps track of shuffled, dealt cards
	ArrayList aCardsFlipped;  //2 cards flipped over
	bool PlayerCanClick;
	bool PlayerHasWon=false;
	
	void Start () 
		{
			PlayerCanClick=true;
			aCards=new Card[totalCards];
			aGrid=new Card[4, 4];
			aCardsFlipped=new ArrayList();
		
			for(int i=0; i<rows; i++) 
			{ 
				//aGrid[,] = new Card[4][]; // Create a new, empty array at index i 
				
				for(int j=0; j<cols; j++) 
				{ 
					aGrid[i,j] = new Card(); 
					//Debug.Log(aGrid[i,j].img);
				} 
			}
		}
	public GUISkin customSkin;
	
		void OnGui()
		{
		GUI.skin=customSkin;
		GUILayout.BeginArea (new Rect (0,0,Screen.width,Screen.height)); 
		BuildGrid();
		GUILayout.EndArea();
		Debug.Log("yay");
		}
	
	void BuildGrid() 
		{ 
		GUILayout.BeginVertical(); 
			for(int i=0; i<rows; i++) 
			{ 
				GUILayout.BeginHorizontal(); 
				for(int j=0; j<cols; j++) 
				{ 
					Card a_card = new Card();
					a_card=aGrid[i, j]; 
					if(GUILayout.Button((Texture2D)Resources.Load(a_card.img),GUILayout.Width(cardW))) 
					{
						//Debug.Log(a_card.img); 
						Debug.Log(cardW);
					} 
				} 
				GUILayout.EndHorizontal(); 
			} 
			GUILayout.EndVertical();
		}
}

Card.cs

using UnityEngine;
using System.Collections;

public class Card : System.Object {
	bool isFaceUp;
	bool isMatched;
	public string img;
	
	public Card ()  //awake is always called before Start
		{
			isFaceUp=false;
			isMatched=false;
			img="robot";
		}
	
	// Update is called once per frame
	void Update () {
	
	}
}
 void OnGUI()
{
// Your stuff
}

Looks like you didn’t spell it correctly. It won’t give you an error because its still a valid method name… But OnGUI is the correct spelling/Caps for Unity

That’s just…shameful. I just tried it. It worked! Thanks. For clarification: the restriction on using a constructor, is that just for classes inheriting from MonoBehaviour? Because as I said, the values weren’t being initialized in “Awake()” so I switched it to a constructor. Or should I use “Start()?”

Ah!!.. I had typed a ton… and it deleted it… because I clicked somewhere while I was grabbing your code.

using UnityEngine;
using System.Collections;

public class Card : System.Object 
{
	bool isFaceUp = false;
	bool isMatched = false;
	public string img = "robot";
}

Simply just do this instead of the constructor.

If you wanted to see this in the inspector since it is your own Custom Data type it wouldn’t be displayed use System.Serializable.

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Card : System.Object 
{
	bool isFaceUp = false;
	bool isMatched = false;
	public string img = "robot";
}

However I don’t think there is anything wrong with Constructors as long as it doesn’t have the Monobehavior as you said.

I am sure you have figured this out by now, but just to clarify it’s not that you should not use constructors when writing in C#, you should just not use constructors on MonoBehaviours, and instead use the Awake() function. Not every class needs to be a MonoBehaviour. It is the difference between is-a and has-a,

I find that in large Unity projects, it’s actually best to abstract away the MonoBehaviour, because all the restrictions placed on it make it very difficult to engage in good object-oriented design. You can’t put it in a namespace, give it a constructor, or any of a variety of other, very handy things.

What I did was create an interface class which inherits from MonoBehaviour, and implements the Observer pattern. That is, it allows other classes to register listeners on it, and calls those listeners any time an event occurs. Then I overrode all of the MonoBehaviour callbacks to call the appropriate event listeners. This class also implements a couple interfaces to manage this, to allow for mocking in unit tests.

This decouples things sufficiently that I can build classes able to do everything MonoBehaviors can, but stuck in external DLLs, in the appropriate places in my namespace and ownership hierarchies, using proper dependency injection, and all that object-oriented goodness.