Use a string to call a class

Hi

I have several classes for each different level of the game. I’m using a state machine approach and when winning or losing a level, a intermediate state is loaded. I’d like to call previous level or next level class by a string, so I don’t need to create a intermediate state for each level.

The name space is Code.States and the classes for each level are named PlayStateLevel1, PlayStateLevel2, etc.

This is my approach:

public WinLoseState (StateManager managerRef)
{
      .
      .
      string currentLevel = "PlayStateLevel" + CalculateLevelNumber ();
      Type type = Type.GetType(string.Format("Code.States.{0}", currentLevel));
      System.Object obj = System.Activator.CreateInstance(type);
}

Then, the idea is to use the obj reference to restart level or go to next level:

if (win)  
{
     .
     .
     manager.SwitchState (new obj (manager));
}

So, instead of using:

manager.SwitchState (new PlayStateLevel2 (manager));

I want to use:

manager.SwitchState (new obj (manager));

But it’s giving me a long error:

MissingMethodException: Method not
found: ‘Default constructor not
found…ctor() of
Code.States.PlayStateLevel1’.

System.Activator.CreateInstance
(System.Type type, Boolean nonPublic)
(at
/Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:368)

System.Activator.CreateInstance
(System.Type type) (at
/Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Activator.cs:254)

Code.States.WinLoseStateLevel1…ctor
(.StateManager managerRef) (at
Assets/Code/States/WinLoseStateLevel1.cs:22)

Code.States.PlayStateLevel1.StateUpdate
() (at
Assets/Code/States/PlayStateLevel1.cs:34)
StateManager.Update () (at
Assets/Code/Scripts/StateManager.cs:37)

I’ve tried several different combinations, but neither seems to work. I’ve followed some instructions, like here: http://forums.asp.net/t/1904441.aspx?Possible+Use+a+string+variable+to+call+a+class+in+C+

Any idea on how to approach this?

There are several issues with your code. Reflection is kinda tricky, so I’ll suggest you make some changes in your code for convenience.

First, you cannot call new on an object (a class that has instantiated). You can only do that with a class. Luckily, Activator.CreateInstance already does that for you (takes the type of a class and calls new and then returns the instantiated object).

However, your PlayStateLevel class has a constructor with a parameter. Now, there is a way to call it by passing your variable, but to make things easy, I suggest you have a constructor with no parameters and simply expose the manager variable so you can set it later.

Finally, you’ll need to cast (explicitly tell C# what type the object is) so you can pass it to your method.

By the way, I assume that all PlayStateLevel class have a base class called PlayStateLevel. If not, I highly recommend you do.

Thus, you will end up with the following:

public WinLoseState (StateManager managerRef)
{
      .
      .
      string currentLevel = "PlayStateLevel" + CalculateLevelNumber ();
      Type type = Type.GetType(string.Format("Code.States.{0}", currentLevel));
      PlayStateLevel pLevel = (PlayStateLevel)System.Activator.CreateInstance(type);
      pLevel.manager = manager;
}

and

manager.SwitchState (pLevel);

Hi pekalicious

Thanks for your reply and suggestions. I still have the same system error as before. The lines triggering the error are these, both for my previous script and your script:

My line:

System.Object obj = System.Activator.CreateInstance(type);

Your line:

PlayStateLevel pLevel = (PlayStateLevel)System.Activator.CreateInstance(type);

I’m not using a PlayStateLevel base class as you mentioned. These are the classes involved so you can have a bigger picture of the design. I’m only including the relevant snippets for each class:

IStateBase class:

namespace Code.Interfaces
{
	public interface IStateBase
	{
	}
}

StateManager class:

using UnityEngine;
using Code.States;
using Code.Interfaces;

public class StateManager : MonoBehaviour {

	private IStateBase activeState;

	public void SwitchState(IStateBase newState)
	{
		activeState = newState;
	}	
}

PlayStateLevel1 class:

using UnityEngine;
using Code.Interfaces;

namespace Code.States
{
	public class PlayStateLevel1 : IStateBase
	{
		private StateManager manager;

		public PlayStateLevel1 (StateManager managerRef)
		{
			manager = managerRef;
			Application.LoadLevel("Level1");
		}

		public void StateUpdate()
		{
			if ( manager.statTracker.GetLifePoints() <= 0) {
				manager.SwitchState (new WinLoseStateLevel1 (manager));
			}			
		}		
	}
}

WinLoseStateLevel1 class:

using UnityEngine;
using Code.Interfaces;

namespace Code.States
{
	public class WinLoseStateLevel1 : IStateBase
	{
		private StateManager manager;
		
		public WinLoseStateLevel1 (StateManager managerRef)
		{
			manager = managerRef;
			
			/*** THIS IS THE CODE I ORIGINALLY ADDED BUT GIVES AN ERROR ***/
			Type type = Type.GetType(string.Format("Code.States.{0}", currentLevel));
       	 System.Object obj = System.Activator.CreateInstance(type)
			/**************************************************************/		
		}
		
		public void ShowIt()
		{				
			if (GUI.Button (new Rect (10, 10, 150, 100), stringNextLevel)) 
			{
				manager.SwitchState (new PlayStateLevel2 (manager));
			}				
				
			if (GUI.Button (new Rect (10, 110, 150, 100), stringPlayLevelAgain)) {
				manager.SwitchState (new PlayStateLevel1 (manager));					
			}			
		}
	}
}

As you can see, the idea is to use a string for:

manager.SwitchState(new STRING (manager));

Any suggestion?