Can a MonoBehaviour be a private inner class? - c#

I want to use a private inner class MonoBehaviour as part of the internal implementation of a class (also a MonoBehaviour). Reduced to the essentials:
public class OuterClass : MonoBehaviour {
public void SomeMethod(GameObject go) {
go.AddComponent<InnerClass>();
}
private class InnerClass : MonoBehaviour {
}
}
This appears to be all working absolutely fine, with no errors or warnings. But it also appears to violate the statement in the Unity documentation that:
The class name and file name must be the same to enable the script
component to be attached to a GameObject.
Is this a problematic violation of that rule? If so, what are the problems? I am aware that it means the script is not visible in the Editor, but that's exactly what I want: it should never be added by anything other than its enclosing class.

Related

When I'm trying to set a value for a base class from a child class (inheritance) it does not work (Unity C#)

I'm creating an enemy (from EnemyCreator1 class) with both EnemyMove1 and MarkusEnemy scripts (EnemyMove1 is a parent class to MarkusEnemy class). In EnemyCreator1 class I set value mainState of the script EnemyMove1 to "CHASE", but when I'm trying to access it from that class it says that mainState is "IDLE" (Please read my coments below because there are more explanations about what am I trying to achieve)
public class EnemyMove1 : MonoBehaviour
{
public enum mainStates { IDLE, CHASE }
public mainStates mainState;
void Update()
{
Debug.Log(mainState); //mainstate == IDLE, but should be CHASE
}
}
public class EnemyCreator1 : MonoBehaviour
{
[SerializeField] private GameObject enemyPrefab;
public void CreateEnemyAndSetItsStateToChase()
{
GameObject enemy = Instantiate(enemyPrefab);
enemy.GetComponent<EnemyMove1>().mainState = EnemyMove1.mainStates.CHASE;
}
}
public class MarkusEnemy : EnemyMove1
{
void Update()
{
EnemyMove enemyMoveScript = GetComponent<EnemyMove>();
Debug.Log(enemyMoveScript.mainState); //mainstate == CHASE
}
}
From the above code it looks like you are inheriting from a different base class EnemyMove, not EnemyMove1.
Thank you guys for helping me, after searching for the information about base classes I decided that it is impossible to access directly it's variables from another objects' scripts so I just simply call methods with variables as arguments (I put variables in round brackets of the method)

C# shared property & field inheritance

I've been trying to perfectly structure this project I'm working on in different classes while maximizing the benefits of inheritance. So far however, it's given me more headaches than benefits.
Consider this:
public class SuperClass : MonoBehaviour
{
[SerializeField] protected Camera _camera;
}
and this
public class SubClass : SuperClass
{
}
Both scripts are attached to different game objects in the scene.
The Camera is to be assigned by dragging it in the inspector
I tried this, and unity seemed to tell me that I had to assign the camera to the SuperClass game object AND to the subclass game object, which makes no sense to me.
How can I assign a camera to SuperClass.cs, which is then used and shared by all of its subclasses?
Thanks in advance!
shared by all of its subclasses
Shared by classes could can only be achieved by using "static" (static variable or singleton).
A workaround could be
public class SubClass :SuperClass
{
[SerializeField] Camera camera;
void Awake()
{
if(camera!=null)
{
_camera=camera;
}
}
// Start is called before the first frame update
void Start()
{
camera=_camera;
}
}
To further extend the solution, you could write a editor script or just get the camera from the code.
You need to create public static Camera property somewhere and reference it in your code, using property:
public static class StaticValues
{
public static Camera Camera {get; set;}
}
public class SuperClass : MonoBehaviour
{
[SerializeField] protected Camera _camera
{
get
{
return StaticValues.Camera;
}
set
{
StaticValues.Camera = value;
}
}
}
public class SubClass : SuperClass
{
}

Why am I able to use variables inside a null reference?

I'm not really sure how to describe it exactly so let me show you what is going on.
I have a PlayerControls script which looks like this (note: I stripped everything except for the necessities).
namespace Player.Controls {
internal class PlayerControls: MonoBehaviour {
public bool IsClimbing { get; private set; } = false;
public bool IsGrounded { get; private set; } = false;
}
}
These variables are set in this class depending if the player is climbing/touching the ground. This script resides on the "Player" GameObject in the scene.
I have another script called PlayerControllerwhich looks like this
using Player.Controls;
public class PlayerController: Singleton<PlayerController> {
internal PlayerStats stats = new PlayerStats();
//PlayerStats nested class (see below)
}
The Singleton class only checks if the generic type is null, if it is, it will use FindObjectOfType to get an instance. This script also resides on the "Player" GameObject.
Inside the PlayerController class, I have a nested class called PlayerStats. It looks like this
internal class PlayerStats : PlayerControls {
public new bool IsClimbing { get { return base.IsClimbing; } }
public new bool IsGrounded { get { return base.IsGrounded; } }
}
Notice this nested class in inheriting from PlayerControls.
The idea is that the PlayerControls class in inaccessible to all other classes except for PlayerController, and any information I want to obtain regarding the player can be obtained by getting the player's instance (via the singleton) and accessing the PlayerStats variable.
For example, assuming the variable inside Singleton which holds the instance is called Instance, one could do PlayerController.Instance.stats.IsClimbing; Everything works as expected, except for one thing.
In the Awake method of the PlayerController class, I do this
private void Awake() {
Debug.LogFormat("In PlayerController Awake(). Is PlayerController.stats null? {0}",
(stats.Equals(null) ? "Yes" : "No"));
Debug.LogFormat("IsClimbing : {0}", stats.IsClimbing);
}
In the output window, it prints
In PlayerController Awake(). Is PlayerController.stats null? Yes
IsClimbing : False
If I also put the same IsClimbing debug in the Update() method, the value is correct for when I start climbing.
So, finally, my question, how can I access the variables of the PlayerStats class with the stats variable if stats is null? I thought it may have been somehow calling straight to the PlayerControls properties, so I changed their names, removed the new inside of PlayerStats and even put a debug statement inside one of the properties inside PlayerStats, and it definitely gets called. For example,public bool IsClimbing { get { Debug.Log("Called IsClimbing inside PlayerStats."); return base.Climbing; } }
If it is getting called and working properly, how can it be null? I asked my professor and he doesn't seem to know why either. What is really going on here?
Edit:
As requested, the Singleton class:
public abstract class Singleton<T>: MonoBehaviour where T : MonoBehaviour {
private static T instance;
public static T Instance {
get {
if(instance == null) {
instance = FindObjectOfType<T>();
}
return instance;
}
}
}
Here is an image of the console output.
Digging around on the Unity forums it appears that the Equals method has been overridden (on Object which MonoBehaviour eventually derives from) which is why comparing a MonoBehaviour to null is not giving you what you might expect. The answer I link to suggests code like this is more appropriate:
stats == null || stats.Equals(null)

Unity: Inspector can't find field of ScriptableObject

I'm having trouble showing a public field of a ScriptableObject which is a child of the component I'm inspecting. While I can easily do this in another way, I need this method to work for other variables. (ReorderableLists)
I simplified the problem, maybe I was just doing something obvious wrong, but I can't see what I'm doing wrong.
Code + error:
http://answers.unity3d.com/storage/temp/70243-error.png
class SomeComponent : MonoBehaviour{
public MyScriptable scriptable; //instantiated and saved as asset
}
[Serializable] class MyScriptable : ScriptableObject{
[SerializeField] public float value = 0.1f;
}
[CustomEditor(typeof(SomeComponent))] class SomeComponentEditor : Editor{
public override void OnInspectorGUI() {
if((target as SomeComponent).scriptable==null) (target as SomeComponent).scriptable = ScriptableObject.CreateInstance(typeof(MyScriptable)) as MyScriptable;
EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptable"));
//shows the asset
EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptable").FindPropertyRelative("value"));
//error
}
}
To fix your code you can do this:
using UnityEditor;
using UnityEngine;
class SomeComponent : MonoBehaviour
{
public MyScriptable myScriptable;
}
class MyScriptable : ScriptableObject
{
public float myChildValue = 0.1f;
}
[CustomEditor(typeof(SomeComponent))]
class SomeComponentEditor : Editor
{
public override void OnInspectorGUI()
{
SomeComponent someComponent = target as SomeComponent;
if (someComponent.myScriptable == null)
someComponent.myScriptable = CreateInstance<MyScriptable>();
SerializedProperty myScriptableProp = serializedObject.FindProperty("myScriptable");
EditorGUILayout.PropertyField(myScriptableProp);
SerializedObject child = new SerializedObject(myScriptableProp.objectReferenceValue);
SerializedProperty myChildValueProp = child.FindProperty("myChildValue");
EditorGUILayout.PropertyField(myChildValueProp);
child.ApplyModifiedProperties();
}
}
To inspect a child object, you first need to create a SerializedObject version of it, which then can be searched for properties as usual.
Also, the Serializable attribute is not needed on classes which derive from ScriptableObject and the SerializeField attribute is only needed when serializing private fields; public fields are serialized by default in Unity.
Without knowing the original context of your code, your approach seems a little peculiar to me. ScriptableObject instances are meant to be used as asset files in your Unity project. Have you used the CreateAssetMenu attribute yet? Usually, you would create your assets manually via the menu and then plug them into your components. The way you are doing it, it won't be written to disk, so why use ScriptableObject and not just a normal class? But maybe it all makes sense in your actual context, so never mind if I'm wrong.
Hack solution found:
SerializedObject newserobj = new SerializedObject(serializedObject.FindProperty("scriptable").objectReferenceValue );
EditorGUILayout.PropertyField(newserobj.FindProperty("value"));
newserobj.ApplyModifiedProperties();

Get Component to the Script vs Instance of the Script itself

In order to get variable(s), function(s) in another class, I have known 2 ways of doing this. First, is to use Get Component to the Script that we want to get the variable(s), function(s) into. Second, is to use Instance of the Script itself.
So I have made the following code:
First case: Get Component to the Script itself
public class Manager : MonoBehaviour
{
private AnotherManager _anotherManager;
private void Awake()
{
_anotherManager = GameObject.Find("Managers").GetComponent<AnotherManager>();
}
private void Start()
{
_anotherManager.myIntVariable = 10;
_anotherManager.MyFunction();
}
}
public class AnotherManager : MonoBehaviour
{
public int myIntVariable;
public void MyFunction()
{
}
}
Second case: Use Instance of the Script itself
public class Manager : MonoBehaviour
{
private void Start()
{
AnotherManager.instance.myIntVariable = 10;
AnotherManager.instance.MyFunction();
}
}
public class AnotherManager : MonoBehaviour
{
public static AnotherManager instance;
public int myIntVariable;
private void Awake()
{
instance = this;
}
public void MyFunction()
{
}
}
My question is: Is there any difference between those cases? In terms of good practice of coding for programmer or performance or it is just a matter of programmer's perspective or whatever else?
Thanks
The second example is the what is known as the Singleton Pattern and should be used very sparingly.
I try to never use the first approach either where you find the gameobject and hope it exists.
You can expose a field for the Unity Inspector so that you can wire it up the same as you can expose any other variable
public AnotherManager AnotherManager;
Alternatively, if you hate using public all over the place like that, like me, you can also indicate to Unity that you wish to expose this variable in the inspector with the SerializeField attribute
[SerializeField]
private AnotherManager anotherManager;
With both of these methods, you can then drag an an object that has the AnotherManager component attached into the field in the inspector.
If instantiated objects need access to this, you will need to wire it up when it is instantiated.
If you need help attaching it in unity I can attach some screenshots.

Categories