Accessing Nested Classes in C#

I tried looking up all I could about how nested classes might be handled different in Unity, and found surprisingly little available. Thus I figured I’d get better results just asking the question directly.

I’m relatively new to coding, and am working on making an ARPG, and am trying to script the ability system for the game. I made a base ability class that contains some consistent variables and methods all abilities would use (like startups and cooldowns), and created a few inherited classes to handle more specific tasks such as attacking and movement. The problem lies in determining how to implement these abilities.

The first approach I tried involved creating a script for each ability, which inherited from these expanded ability classes, like a walk class that inherits from the movement abiltiy class. This worked, but seemed like it would quickly become completely unmanageable (for instance making an ability script for each swing of a sword combo).

Instead, I’ve recently been trying to see if nested classes might work for my purpose. I figured I could create the classes from the previous separate scripts files as nested classes within the relevant script that would use it, such as a movement class which has access to all the movement actions a character might use, resulting in the piece of code below. The problem seems to lie in initializing the nested classes themselves, as whenever I run the scene in unity, I receive this error from Unity: “Can’t add script behaviour . The scripts file name does not match the name of the class defined in the script!”
Does the problem lie in how I’m using AddComponent? How does one actually create an instance of a nested class in Unity?

Thanks for whatever help you might be able to lend.

using UnityEngine;
using System;
using System.Collections;

public class BaseMovement : MonoBehaviour
{
	public enum MoveState {
		Idle,
		Turning,
		Aiming,
		Walking,
		Running,
		Dodging
	}
	
	#region Class Variables and Members
	//Specialization
	private Transform _user;													
	private CharacterController _body;											
	private MoveState _state;
		
	//Movement Abilities
	protected BaseMovement.BaseAim aim;
	protected BaseMovement.BaseTurn turn;
	protected BaseMovement.BaseWalk walk;
	protected BaseMovement.BaseRun run;
	protected BaseMovement.BaseDodge dodge;
	
	//Internal Coordination
	private Vector3 _moveDir;
	private Vector3 _lookDir;
	#endregion
	
	#region Getters and Setters
	public MoveState State {
		get {return _state;}
		set {_state = value;}
	}
	public bool Grounded {
		get {return _grounded;}
		set {_grounded = value;}
	}
	
	public Transform User {
		get {return _user;}
		set {_user = value;}
	}
	
	public BaseMovement.BaseAim Aim
	{
		get {return aim;}
		set {aim = value;}
	}
	public BaseMovement.BaseTurn Turn
	{
		get {return turn;}
		set {turn = value;}
	}
	public BaseMovement.BaseWalk Walk
	{
		get {return walk;}
		set {walk = value;}
	}
	public BaseMovement.BaseRun Run
	{
		get {return run;}
		set {run = value;}
	}
	public BaseMovement.BaseDodge Dodge
	{
		get {return dodge;}
		set {dodge = value;}
	}
	#endregion
	
	
	protected virtual void Awake () {
	
		_user = GetComponent<Transform>();
		_body = GetComponent<CharacterController>();
		
		if (_body == null) return;
		
		_state = MoveState.Idle;
		_moveDir = Vector3.zero;
		_lookDir = Vector3.zero;
		
		
		
		aim = gameObject.AddComponent<BaseMovement.BaseAim>();
		turn = gameObject.AddComponent<BaseMovement.BaseTurn>();
		walk = gameObject.AddComponent<BaseMovement.BaseWalk>();
		run = gameObject.AddComponent<BaseMovement.BaseRun>();
		dodge = gameObject.AddComponent<BaseMovement.BaseDodge>();
		Debug.Log ("MOVEMENT PLUGINS CREATED");
	}
	
	private void SetMoveState (BaseMovement.MoveState state) {
		_state = state;	
	}
	
	#region Movement Functions	
	public void Gravity () {
		Vector3 down = -Vector3.forward;
		Vector3 gravity = new Vector3(0, 0, -10);				//strength of gravity
		float distanceToGround = 2;								//units above map plane to be considered "grounded"
		
		RaycastHit hit;
		if (Physics.Raycast (User.position, down, out hit)) {
			if (hit.collider.tag == "Ground") {
				if (distanceToGround <= hit.distance) {
					_grounded = false;					
					_body.Move(gravity);
				} else {
					_grounded = true;
				}
				
			} else {
				Debug.LogWarning("Character did not find any ground");	
			}
		}
	}
	
	protected class BaseAim : MovementAbility
	{
		BaseMovement _movement;
		
		protected override void Awake ()
		{
			base.Awake ();
			_movement = GetComponent<BaseMovement>();
			Name = "Aim";
			Effect = AimAbility;
			Speed = 0.0f;
			
			StartupTime = 0.0f;
			DurationTime = 0.0f;
			CooldownTime = 0.0f;
		}	
		
		private void AimAbility (Vector3 target) 
		{
			//_state = MoveState.Aiming;
			Vector3 object_pos = Camera.main.WorldToScreenPoint(User.position);			
			float angle;
			
			target.x = target.x - object_pos.x;
		        target.y = target.y - object_pos.y;
			angle = Mathf.Atan2(target.x, target.y) * Mathf.Rad2Deg;
	    	
			User.rotation = Quaternion.Euler(0, 0, angle);
		}
	}
	
	protected class BaseTurn : MovementAbility
	{
		BaseMovement _movement;
		
		protected override void Awake ()
		{
			base.Awake ();
			_movement = GetComponent<BaseMovement>();
			Name = "Turn";
			Effect = TurnAbility;			
			Speed = 3.0f;
				
			StartupTime = 0.0f;
			DurationTime = 0.0f;
			CooldownTime = 0.0f;
		}		
		
		private void TurnAbility (Vector3 target) 
		{
			//_state = MoveState.Turning;
			float aim = Mathf.Atan2(-target.x, target.y) * Mathf.Rad2Deg;					
			User.rotation = Quaternion.Euler(0, 0, aim);
		}	
	}	
	
	protected class BaseWalk : MovementAbility
	{
			protected override void Awake ()
		{
			base.Awake ();
			Name = "Walk";
			Effect = WalkAbility;
			Speed = 7.0f;
				
			StartupTime = 0.0f;
			DurationTime = 0.0f;
			CooldownTime = 0.0f;
		}		
		private void WalkAbility ( Vector3 dir) 
		{
			//_state = MoveState.Walking;
			Vector3 moveDir = dir.normalized;				//holds direction the character will move in
			moveDir *= Speed;
			if (Body != null)
				Body.Move (moveDir * Time.deltaTime);
			else
				Debug.Log("No controller to move");
		}
	}
	
	protected class BaseRun : MovementAbility
	{
		protected override void Awake ()
		{
			base.Awake ();
			Name = "Run";
			Effect = RunAbility;
			Speed = 15.0f;
				
			StartupTime = 0.0f;
			DurationTime = 0.0f;
			CooldownTime = 0.0f;
		}		
		
		private void RunAbility ( Vector3 dir) 
		{
			//_state = MoveState.Running;
			Vector3 moveDir = dir.normalized;				//holds direction the character will move in
			moveDir *= Speed;
			if (Body != null)
				Body.Move (moveDir * Time.deltaTime);
			else
				Debug.Log("No controller to move");
		}
	}
	
	protected class BaseDodge : MovementAbility
	{
		protected override void Awake ()
		{
			base.Awake ();
			Name = "Dodge";
			Effect = DodgeAbility;
			Speed = 40.0f;
				
			StartupTime = 0.0f;
			DurationTime = 0.20f;
			CooldownTime = 0.0f;
		}	
		private void DodgeAbility (Vector3 dir)
		{
			//_state = MoveState.Dodging;
			Debug.Log ("DODGE ACTIVATED");
			
			Vector3 moveDir = dir.normalized;
			moveDir *= Speed;
			
			if (Body != null)
				Body.Move (moveDir * Time.deltaTime);
			else
				Debug.Log("No controller to move");
		}
	}
	#endregion
}

using UnityEngine;
using System.Collections;

public class Example : MonoBehaviour
{
    [System.Serializable]
    public class Test
    {
        public int testInt = 5;
        public string label = "Label";
    }

    public Test test = new Test();
}

This looks like a design you’re gonna regret real soon… it’s too heavy

First of all, your a classes don’t need to be Monobehaviours.
You could easily have just basic classes and call them from your MonoBehaviours - They don’t need their own Update etc. being called from Unity’s main loop.

Also, all classes you have derived from MovementAbility are basically just Configurations of the MovementAbility. Why have classes at all then? Just create new instances of MovementAbility and configure them. If you need different Behaviour on a subclass like in WalkAbility vs RunAbility, think about solving this with parameters first, like the code you have in there is the same and depends on speed and maybe MoveState later - these are parameters of the class, so why have a new class?

Try to allover go to a more configurable Design, so you could let this use an artist with the inspector without having to add new code all the time.

Keep all your Abilities in a List on a Manager GameObject in a Prefab.
If you think you may lose them or want to persist them somewhere else, Implement saving and loading from files.

Also please read this:
http://gameprogrammingpatterns.com/type-object.html
http://gameprogrammingpatterns.com/prototype.html

A component must extend MonoBehaviour (at some point in its hierarchy, doesn’t have to be a direct extension).

Also, all components must be in their own script, which is named after the one MonoBehaviour-extending class in that script (there can be multiple classes, but only one can extend MonoBehaviour and also be AddComponent-able).

For instance, you could AddComponent(C), but you cannot AddComponent(D) or (B):

File: A.cs

class A : MonoBehaviour {
}
class B : A {
}

File: C.cs

class C : A {
}
class D : A {
}

I’m not sure about E, but just don’t do this. It’s silly. :smiley:

File: E.cs

class E : B { }

In short, best to keep to one MonoBehaviour derivative per file.

I don’t know the specifics about declaring classes inside classes - is there functionality to that besides scoping?

Edited. I wish actually did what is supposed to do :stuck_out_tongue: