Unity Inheritance & scope [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 months ago.
Improve this question
C++ Developer trying to get started in Unity here.
I've been playing around in Unity and watching some videos / reading the documentation.
I have a couple of questions on inheritance and I hoping someone can point me to something helpful.
MonoBehaviour: this seems to be the standard, most scripts will use this, it is attached to a gameobject and is 'generally' destroyed with the scene.
ScriptableObject: allows you to create 'assets' outside of the scene, which have persistence.
: testing has shown that you can create 'normal' classes and Instantiate them inside of inherited classes with 'new'.
Is it possible to inherit from more than one class? I assume so, but what are the rules here?
Can I do:
Baseclass
SubClass : ScriptableObject, Baseclass
OR
Baseclass
SubClass : MonoBehaviour, Baseclass
etc?
Or do I have to use the same inheritance everytime? ie.
Baseclass : ScriptableObject
SubClass : ScriptableObject, Baseclass
Also can how do I get a 'ScriptableObject' into a scene?
I have managed to do as so:
using UnityEngine;
[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/PlayerData", order = 2)]
public class PlayerData: ScriptableObject
{
public int maxHealth;
public PlayerDictionary.PlayerEnum playerEnum;
}
using System.Collections.Generic;
using UnityEngine;
public class PlayerDictionary : MonoBehaviour
{
[SerializeField] private PlayerData[] playersInGroup;
private readonly Dictionary<PlayerEnum, PlayerData> playerFinder = new Dictionary<PlayerEnum, PlayerData>();
private void Awake()
{
for (int x = 0; x <= playersInGroup.GetUpperBound(0); x++)
{
playerFinder.Add(playersInGroup[x].playerEnum, playersInGroup[x]);
}
}
public PlayerData FindPlayer(PlayerEnum type)
{
PlayerData ret = null;
playerFinder.TryGetValue(type, out ret);
return ret;
}
public enum PlayerEnum
{
TILE_TYPE_NONE = 0,
TILE_TYPE_ENEMY,
TILE_TYPE_ENEMY_ALT1,
TILE_TYPE_ENEMY_ALT2
}
}
One final thing:
How do keep things in scope?
I have two ways;
static vars in a class, the is not in the scene at all.
DontDestroyOnLoad(gameObject);
Are there other / better methods to keep things in scope when switching scenes?

Is it possible to inherit from more than one class?
No, you can't inherit from multiple classes, but you can implement multiple interfaces
I have two ways;
static vars in a class, the is not in the scene at all.
DontDestroyOnLoad(gameObject);
This is the preferred way of keeping game objects across all scenes, this can be achieved by the following code
public class SomeClass : MonoBehaviour
{
public static SomeClass instance;
// other variables
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
// other methods
}
A bit of explanation here, the first time the scene loads, instance is null. In the Awake() method, we set instance to this, which refers to this script and we set DontDestroyOnLoad(). So when you switch scenes, the gameobject to which this script is attached stays in the hierarchy. But when you reload the same scene, you don't want a duplicate of this gameobject. If you are reloading into this same scene, all the gameobjects get created in which there will also be a gameobject that was set to DontDestroyOnLoad() previously. Now in the newly created gameobject, the script's instance is not null this time, hence it gets destroyed. Finally, there's only one gameobject with this script, that was previously set to DontDestroyOnLoad().

Related

Is it the right way to reference other script if it doesn't have MonoBehaviour?

I'm making a saving system for my mobile game. I have a script for economy that tracks the amount of in-game currency and i'd like to reference a non MonoBehaviour script that will hold the data to save
public class Money : MonoBehaviour
{
public int Gold;
public int Platinum;
public int Tokens;
public DataHolder data;
private void Update()
{
data.Platinum = Platinum;
data.Tokens = Tokens;
data.Gold = Gold;
}
}
public class DataHolder
{
public int[] dragonLevel;
public bool[] dragonMasterLevel;
public int Gold;
public int Platinum;
public int Tokens;
}
Will unity automatically get the reference and will it properly transfer this data?
You are allowed to use your own classes / structs in Unity. Unlike Unity components like ScriptableObject and MonoBehaviour, you need to create them and make sure they get deleted.
ScriptableObject
public class DataHolder : ScriptableObject {
...
}
ScriptableObjects are Unity's solution for exactly your use case. You can then create assets that are instances of DataHolder.
In this solution the data gets serialized with the GameObject so you can have default values in Unity Editor Inspector.
Keep using DataHolder as is
private void Update () {
if (data == null) data = new DataHolder();
...
}
In both these solutions the runtime values will not persist between sessions. To save values and restore them there are different options. Here are a few in order of simplicity:
PlayerPrefs:
Add WriteToPrefs() and LoadFromPrefs() methods to DataHolder.
FileSystem
online databases like Google Firebase
Adding to the other answer, you could just create an object of your DataHolder class in one of your monobehaviours, and call DontDestroyOnLoad on the gameObject. you will also need to use the code below in Awake on your DontDestroyOnLoad() object to make sure only one instance of the MonoBehaviour is available, so you dont create duplicates when you navigate between scenes
public DataHolder dataHolder;
public static MyComponent myComponent;
private void Awake()
{
DontDestroyOnLoad(gameObject);
if (myComponent == null) myComponent = this;
else Destroy(gameObject);
dataHolder = new DataHolder();
}
now you can write to this dataHolder object and keep the GameObject alive in case you want to add any more data to it. It is good practice to have a persistant GameObject in the scene to save in-game data, or just about anything that needs to be preserved between scenes.
Alternatively, you can also make your DataHolder class static and it's members static. although i would advise against this, especially if there are multiple players/characters in your game that use the same component. But this will actually help you reference it from any script, without creating objects, or maintaining a persistent GameObject in the scene.

Calling an Object from another Script

This might be a super simple question, but for some reason I can't get it to work:
I have two scripts, both attached to the same GameObject.
One script has a dictionary:
public class RPG_Implementierung : MonoBehaviour
{
public Dictionary<string, string> StoryText = new Dictionary<string, string>();
void Start()
{
StoryText.Add("1", "This is the first Entry");
}
}
The other script wants to call that Dictionary. The method SendMessageToChat` is defined in this script and works well as long as it's not referencing the other script.
The first thing I tried didn't work, I get the Error:
CS0120 An object reference is required for the non-static field, method, or property
public class GameManager : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Y))
{
SendMessageToChat(RPG_Implementierung.StoryText["1"]);
}
}
}
I
this also doesn't work, it gives me the Error
CS0119 'RPG_Implementierung' is a type, which is not valid in the given context
public class GameManager : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Y))
{
SendMessageToChat(GetComponent(RPG_Implementierung).StoryText["1"]);
}
}
}
Can someone please tell me what I did wrong? In standard C# all I would have to do is to set the other class to public and then I can reference it and access it's objects, why doesn't this work in Unity?
To reference another component on a GameObject, you will need to grab that reference either by serializing the field in the inspector (Making it public or using the attribute [SerializeField].
I am not sure how many places you want to eventually call the method you are trying to invoke, but if it is from a bunch of different places, you might want to consider the Singleton pattern.
To quickly fix your current issue, on your GameManager.cs, do one of these two things:
public class GameManager : MonoBehaviour
{
[SerializeField] private RPG_Implementierung rpgImplement = null;
// OR
public RPG_Implementierung rpgImplement;
void Update()
{
if (Input.GetKeyDown(KeyCode.Y))
{
SendMessageToChat(rpgImplement.StoryText["1"]);
}
}
}
Edit: If you want to use the GetComponent in the Update here is how you would call it. I would advise against this as calling a GetComponent in an Update can be quite costly for performance if called frequently. It is better to store the reference to later use.
public class GameManager : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Y))
{
SendMessageToChat(GetComponent<RPG_Implementierung>().StoryText["1"]);
}
}
}

Inspector value cannot access from another class in Unity3d

I have two classes. One called GameManager and another one Enemies.
I have two variables in GameManager which I have changed from inspector currentLevel=1 and totalEnemy=10.
// GameManager.cs
private static GameManager instance = new GameManager();
public static GameManager get(){ return instance; }
public int currentLevel;
public int curLevel { get; set; }
public int totalEnemy;
public int totLevel { get; set; }
void Start () {
curLevel = currentLevel;
totLevel = totalEnemy;
}
I'm trying to access these two variable from Eneimes class like this; but everytime it gives me curLevel = 0, but I'm expecting to get curLevel = 1. What I'm doing wrong?
// Enemies.cs
void Start () {
Debug.Log (GameManager.get().curLevel); // always output = 0
}
The line private static GameManager instance = new GameManager(); is the issue.
When a script is attached to a GameObject, an instance of the type of the script is referenced as this inside the script. In other words, there can be multiple instances of same type if the same script is attached to multiple GameObjects.
Therefore, the specific instance that have curLevel = 1 as you set in the Inspector is an instance of the type attached to the specific GameObject. This means the one should be referred to as this inside the script.
If you declare a new instance of GameManager as in your code, you are basically ignoring all values in the Inspector because the static GameManager instance is pointing to a different instance than the instance you set values for in the Inspector.
In order to use the specific instance that you declared using the Inspector, you should do the following.
using System.Collections.Generic;
using System.Collections;
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
public static GameManager get() { return instance; }
public int currentLevel;
public int curLevel { get; set; }
public int totalEnemy;
public int totLevel { get; set; }
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Debug.LogError(string.Format("GameManager.Awake(): More than one instances of this type {0} is being initialised but it's meant to be Singleton and should not be initialised twice. It is currently being initialised under the GameObject {1}.", this.GetType(), this.gameObject.name));
Destroy(gameObject);
}
curLevel = currentLevel;
totLevel = totalEnemy;
}
}
Note that I changed Start() to Awake(). This is because you are referring to values initiliased in this method from other scripts, and you cannot guarantee which Start() is called first between different MonoBehaviours in the runtime. However, Unity guarantees that Awake() is always called earlier than Start(). Further, it is Unity's best practice to initialise self-initialisable variables in Awake(), and initialise variables dependent on other scripts in Start() because of this execution order.
Lastly, there will be problems when there are multiple GameObject that has GameManager as its component in your scene. Consider a case where you have two such objects. when the scene is loaded, each of the script will call Awake(), and both of them will set private static GameManager instance; to each of the two this. The result would be one is overriden by another.
You could say that you will be careful to use this script and make sure only one GameObject has this script as its component. However, you should always write your code as if someone who do not know about your code can use it without thinking, and stupid mistakes of other people new to the project could be easily detected.
EDIT:
To respond to the OP's comment, I added code to handle when this type is initialised more than once in the project. In addition to #Kardux's suggestion, I added Debug.LogError() because I do not want the project to silently solve things. If a problem happens, I want to get notified of it.
If you are using Singletons frequently in your project, you might want to have a parent abstract class Singleton that handles this instance checking process for all child Singletons, and have GameManager inherit from Singleton.
However, use Singleton with care as it is considered a bad design pattern if misused. (And I don't know how to use it properly so I avoid using it.)

Access variable from another non MonoBehaviour class

Hi I would like to get a variable from another class in Unity
public class CameraMove : MonoBehaviour {
public GameObject lookTarget;
public GameObject MainCamera;
public GameObject nextMovePoint;
private int currentPoint = 0;
[System.Serializable]
public class Paths
{
public float time;
public Transform[] movePoints;
}
public Paths[] Path;
void Update()
{
Paths paths = gameObject.GetComponent<Paths>();
Debug.Log (paths.movePoints [currentPoint]);
if (nextMovePoint.transform.position != paths.movePoints [currentPoint].position)
{
currentPoint += 1;
nextMovePoint.transform.position = paths.movePoints [currentPoint].position;
}
iTween.MoveTo(nextMovePoint,iTween.Hash("time",paths.time));
}
I want to get the movePoints from class Paths, but GetComponent gives me an error? How do I get that variable?
Still struggling with this, anyone got an idea?
You do not need to use GetComponent to get access to your paths.
First, I'm going to clean up your naming a little bit so that what we're talking about a little clearer:
public class CameraMovement : MonoBehaviour {
public GameObject LookTarget;
public GameObject MainCamera;
public GameObject NextMovePoint;
public Path[] Paths;
private int _currentPoint = 0;
}
[System.Serializable]
public class Path {
public float Time;
public Transform[] MovePoints;
}
As I understand it, you want to use your Paths in your Update event. Because Paths is just a regular variable on the component, like LookTarget or MainCamera, you can just refer to it directly. Thus:
void Update() {
Path path = Paths[/*some index*/];
Debug.Log(path.MovePoints[_currentPoint]);
// etc.
}
GetComponent is used to grab a different MonoBehaviour which is attached to the game object. When it's part of the serialized information, it's already on your class and you can just use it directly.
From you code I see that your Path class is inner(local class) of your CameraMove class. So I think you want to move it to separate class(read like file) and attach that as component to what ever game object you need. So then you can use getComponent<Path>() on that object. However if you want Path class to be inner class so you need to initiate (in Start())it like
Path[0]=new Path(); //etc
Then you can use it inside your CameraMove class as
Path[0].movePoints[currentPoint];
But if you explain more what you want to achieve then I can provide more accurate solution.
The primary objective of GetComponent is to get a component attached to the game object. This means that GetComponent can only get objects of classes deriving from UnityEngine.Component. Note that MonoBehaviour is a subclass of Component, so any and all MonoBehaviour subclasses are subclasses of Component. Also note that only components, i.e. classes deriving Component show up in the inspector of a game object.
The class Paths does not inherit the class Component, either directly or indirectly. Thus it can't be added to an object or retrieved from an object using GetComponent.
As you specify in the comments, an object of type CameraMove.Paths is in another component of type CameraMove. If the other CameraMove component is on another game object, things become incredibly easy. You won't even need GetComponent for this. Just create a field public CameraMove otherCameraMove (I prefer a private field with the SerializeField attribute, but it doesn't matter here since you're obviously new to Unity) and select the other CameraMove object in the inspector. You'll be able to access the paths variable as otherCameraMove.Path.
If the other CameraMove component is on the same object however, things get more tricky. I, for one, suggest that game objects should never, ever have more than one instance of the same component. But if you do, you'll have to use GetComponents<CameraMove>() to retrieve all components of type CameraMove on the game object and iterate through the array looking for the CameraMove component which you want. This is just one of the reasons why in my opinion same components shouldn't be added to the same object more than once.

Unity3D - Abstract Class, Monobehaviour functionality

PREAMBLE
So I've been floating this question around on the Unity Answers forum for the past couple of weeks without a single reply. It seems to me that it would be a relatively straight forward question so I was wondering if anyone here might be able to help me with it in lieu of an answer over there.
So I have a bit of a dilemma.
GOAL
I want to use an inheritance structure for guns in my game. I want to make an abstract class (Gun) and then have subclasses of Gun (Laser, Rifle, RocketLauncher) which inherit from it and then create specific instances of these weapons. At the moment I'm doing this as an abstract class because there's a large variance between the way different subclasses of Gun implement the Shoot() function. (i.e. A rocket launcher will instantiate X Rocket GameObjects whereas a laser will probably use raycasting to determine an instant hit).
Furthermore, I would like to be able to have a List of Guns in the Players inventory and don't want to have to add and remove Scripts from my Player dependant on their active weapon. It's just nice for me if I can construct them in the following fashion:
Rifle ActiveWeapon = new Rifle(parameters);
I also don't want to have a finite number of weapons available to the player but want to be able to generate them at runtime (as drops or crafted for example) so I also don't want to write a fixed number weapon scripts.
ISSUE
The issue I'm encountering is that I want to be able to utilise some of the methods which are derivative of MonoBehaviours for example :
public override void Shoot ()
{
if (CanShoot)
{
StartCoroutine(Cooldown());
Rigidbody bullet = Rigidbody.Instantiate(BulletPrefab, transform.position, transform.rotation);
... // Addforce etc.
...
}
}
public IEnumerator Cooldown()
{
CanShoot = false;
yield return new WaitForSeconds (FireRate);
CanShoot = true;
}
Obviously since this isn't a MonoBehaviour class I can't use functions such as Instantiate to create bullets or WaitForSeconds to stagger them.
How should I tackle this issue?
You can have an abstract class that inherits from MonoBehaviour, the following works fine:
public abstract class AbstractClass : MonoBehaviour
{
protected abstract void pouet();
}
public class InheritClass : AbstractClass
{
// Use this for initialization
void Start ()
{
StartCoroutine(pouetCoroutine());
}
protected override void pouet()
{
Debug.Log("pouet");
}
private IEnumerator pouetCoroutine()
{
yield return null;
pouet();
}
}
If you don't want your abstract class to be in a GameObject, you could use an external gameobject to manage the instanciation with an object pooling system.
About the coroutines, you could use a coroutine engine that starts and stop them. You only need to have a dictionary and a public function to start the coroutine and keep an id as a key to access it if you need to stop it later.
private startcoroutine(int id, IEnumerator ienum)
{
Coroutine c = StartCoroutine(ienum);
dictionary.Add(id, c);
}

Categories

Resources