How can I use inheritance in Unity effectively? - c#

I have basic understanding of inheritance but since every script I want as a component on a game object has to inherit from MonoBehaviour things get tricky.
Let's say I want to have a base class called Character and I want Player and Enemy to derive from Character. How am I suppose to add Player and Enemy script to Player and Enemy objects without getting an error?
I also tried to create empty game object and add CharacterStats to it then populate this class with different character objects with different stats. Guess what, you can't use new keyword to create objects if the script derives from Monobehaviour.
Is there some tutorials about this topic to make it more clear?

Probably the thing to understand is that MonoBehaviour itself inherits Component - A MonoBehaviour IS a Component.
When you look at it as if Components are a part of a thing, it makes at least some sense that you can't just new a Component - could I make a new Arm? If I did, where would it got? But I could make a new Body body and then dobody.AddComponent<Arm>, right? Because then it's clear what happens to the Arm - it gets attached to the body.
Similarly you can't just new a Component because they're supposed to be a part of a GameObject. What you can do is to make a new GameObject and .AddComponent<>() to that object. Again, now it's clear where that Component is going.
I generally agree with UnholySheep's comment that you should prefer composition over inheritance because this generally makes your code more modular. That said, I definitely do also use inheritance.
The question you should be asking yourself when you're outlining your class is, "Is this new class a KIND of _____ or does this new class HAVE a _____." And it's easy I guess to say a Player is a Character and an Enemy is a Character, but do you need to subclass? Or would a Player just be a Character that also has a PlayerController? Maybe you could have an interface like IPlayerController and have a LocalPlayerController for the user and an AIPlayerController for the enemies? Lots of ways to problem-solve with programming.
Or maybe all Characters have a PlayerController and just the .activeControl bit is false for non-player characters. Then you could do things like ghost/spectate NPCs, etc.
But to your question specifically:
Let's say I want to have a base class called Character and I want Player and Enemy to derive from Character. How am I suppose to add Player and Enemy script to Player and Enemy objects without getting an error?
You would start with a Character script that inherits MonoBehaviour and implements any of the logic that is common to both classes:
public class Character : MonoBehaviour
{
// Your common character logic
}
Then you would have subclasses inherit Character:
public class Player : Character
{
// Your Player-specific logic here
}
and
public class Enemy : Character
{
// Your Enemy-specific logic here
}
Then you either add the Enemy and Player scripts to a GameObject in the editor or you can do it programmatically by getting a reference to the target GameObject (by setting the reference in editor or by creating a new GameObject in a script) and then you call targetGameObject.AddComponent<YourComponentToAdd>();.

Using inheritance well comes from planning and iterating. Perhaps your Character class doesn't work well as a class for your enemies. I tend to have the player in their own class (unless its multiplayer) with enemies using their own base class. Below is an example of some inheritance I use in a 2D game I'm working on.
Base Class:
Enemy.cs
public abstract class Enemy : MonoBehaviour
{
public Vector2 flyInPos;
public Vector2 startPos;
public bool isFlyingIn;
public float flyInDuration = 5000f;
public float flyInTime = 0f;
public abstract void Destroy();
public abstract void DestroyWithoutScore();
public abstract void Attack();
public abstract void Attack2();
public abstract void Attack3();
public abstract void FlyOut();
public virtual void FlyIn()
{
isFlyingIn = true;
}
}
Every enemy needs to have these elements. You'll notice most of the methods in my base class are marked abstract so that each class that inherits can have a unique set of Attacks, and Flying patterns. One thing to notice is that the base class is inheriting from MonoBehaviour. Here is how I implemented inheritance in one of my enemies. Sorry its still a WIP.
Inherited Class:
ShootingDrone.cs
public class ShootingDrone : Enemy
{
public GameObject projectileSpawner;
public GameObject projectile;
public void Start()
{
startPos = transform.position;
Scheduler.Invoke(Attack, 5f, gameObject);
}
public void Update()
{
if(isFlyingIn)
{
transform.position = Vector3.Lerp(startPos, flyInPos, flyInTime / flyInDuration);
flyInTime += Time.deltaTime;
}
}
public override void Attack()
{
Shoot();
Scheduler.Invoke(Shoot, 0.25f, gameObject);
Scheduler.Invoke(Shoot, 0.5f, gameObject);
}
public override void Attack2()
{
Attack();
}
public override void Attack3()
{
Attack();
}
public void Shoot()
{
GameObject newProjectile = Instantiate(projectile, projectileSpawner.transform);
newProjectile.transform.parent = null;
}
public override void FlyOut()
{
throw new System.NotImplementedException();
}
public override void DestroyWithoutScore()
{
throw new System.NotImplementedException();
}
public override void Destroy()
{
Destroy(gameObject);
}
}
The base class of Enemy inherits from MonoBehaviour. So when Shooting Drone inherits from Enemy it also is inheriting from MonoBehaviour.
For your problem of trying to add a new MonoBehaviour. The reason for this is that a MonoBehavior HAS to be attached to something. Imagine a Rigidbody with no GameObject, it doesn't work. If you wanted to make a "new" CharacterStats the way you would do that is:
CharacterStats stats = gameObject.AddComponent<CharacterStats>();
If you don't want your CharacterStats as a component, then simply remove the MonoBehavior inheritance from the class and instantiate it as new CharacterStats.
There are a variety of tutorials that cover inheritance, but depending on how new you are to the subject I would start with the Unity official inheritance tutorial. I do think this tutorial is too brief but I also like CircutStreams tutorial since it also mentions implementing Interfaces which can be a better solution to inheritance in many cases.

Related

How to create a MonoBehaviour fabric for non-MonoBehaviour classes?

I am developing a small nuclear reactor simulator game. I have a bunch of reactor component classes: HeatVent, HeatExchanger, UraniumCell etc. They are not deriving from MonoBehaviour since they don't have any Unity logic, but they do implement a shared interface IReactorComponent. What I want to do is to be able to create prefabs of such components (simple heat vent, advanced heat vent, doubled uranium cell etc.) The prefabs would have different sprites and something like that, but the main issue is to define what reactor component class the prefab is related to, because I can't just drag'n'drop a non-MonoBehaviour script on inspector. Also, I want to be able to set settings in the inspector (for example, HeatVent has CoolAmount and HeatCapacity properties, UraniumCell has FuelAmount, HeatProduce and PowerProduce properties).
I have read about factory method pattern and as I understood, I have to create a fabric class that derives from MonoBehaviour for each reactor component class like HeatVentBehaviour, HeatExchangerBehaviour etc. Yes, that completely solves my issue with prefabs but is there any way to not create an additional MonoBehaviour wrap for each class? If I had 15 IReactorComponent classes, I would need to create 15 fabrics which feels like not the greatest solution.
Sounds like what you are looking for is ScriptableObject!
Instances of those are assets so they don't live in a scene but in the Assets folder and basically behave a little bit like prefabs except: They already exist and do not need to be instantiated anymore.
Mostly they are used as just configurable data containers. They have an Inspector so you can easily fill them with your desired data and references to other assets (e.g. the related prefab in your case).
But in addition you can as well let them implement behavior like your interface and thereby change the behavior of your scene objects by using different implementations of a method from different ScriptableObjects!
For the factory you then only need to figure out for which method to use which ScriptableObject instance e.g. either by having different methods or by having a Dictionary where you fill in your SO references.
Just as an example how this might look like (make sure each MonoBehaviour and ScriptableObject has its individual script file with matching name)
SpawnManager.cs
public class SpawnManager : MonoBehaviour
{
[SerializeField] private ReactorComponentBehaviour _behaviourPrefab;
[SerializeField] private BaseReactorComponent[] _components;
public bool TrySpawn<T>(out T component, out ReactorComponentBehaviour componentBehaviour) where T : IReactorComponent
{
component = default(T);
componentBehaviour = default;
var foundComponent = components.FirstOrDefault(c => c.GetType() == typeof(T));
if(foundComponent == null)
{
Debug.LogError($"No component found of type {T.GetType().Name}!");
return false;
}
// Here Instantiate doesn't spawn anything into the scene but
// rather creates a copy of the ScriptableObject asset
// This is just to avoid that any changes in the fields during the game
// would change the original ScriptableObject asset and thereby ALL related behavior instances
component = Instantiate ( (T) foundComponent);
// This now indeed spawns the related MonoBehaviour + GameOver
componentBehaviour = Instantiate (behaviourPrefab);
componentBehaviour.Init(component);
return true;
}
}
BaseReactorComponent.cs
public abstract class BaseReactorComponent : ScriptableObject, IReactorComponent
{
public abstract void WhateverIReactorComponentNeeds();
// Common fields and methods e.g.
public Sprite Icon;
}
HeatVent.cs
[CreateAssetMenu]
public class HeatVent : BaseReactorComponent
{
public int CoolAmount;
public int HeatCapacity;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
UraniumCell.cs
[CreateAssetMenu]
public class UraniumCell : BaseReactorComponent
{
public int FuelAmount;
public int HeatProduce;
public int PowerProduce;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
And finally you need only one base prefab with the
ReactorComponentBehavior.cs
public class ReactorComponentBehavior : MonoBehaviour
{
[SerializeField] private Image _image;
private IReactorComponent _component;
public void Init(IReactorComponent component)
{
_componemt = component;
// Do other stuff like e.g. adjust visuals according to the component etc
_image.sprite = component.Icon;
}
// And then use whatever this behavior should do with the assigned component
}
So in the end you would use that like e.g.
if(spawManagerReference.TrySpawn<HeatVent>(out var component, out var componentBehaviour)
{
// Do something with the behavior e.g. set its position, parent etc
}
else
{
Debug.LogError($"Failed to get a {nameof(HeatVent)}!");
}
If then at some point you still want different additional behaviours you could let them inherit from the common ReactorComponentBehavior and rather reference the prefabs inside the BaseReactorComponent itself .. then every component can bring its own prefab but still have a common core behaviour

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
{
}

Add script inheriting from scriptableobject to game object

I have a script called Weapon.cs, which is a scriptable object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class Weapon : ScriptableObject {
protected Camera _mainCamera;
protected Transform _weaponTransform;
protected int _damage;
protected int _fireRatePerSecond;
protected bool _isAutomaticWeapon;
protected void FireWeapon()
{
//if the weapon is automatic
if (_isAutomaticWeapon)
{
ShootRapidFireOnMouseHold();
}
//if the weapon is semi automatic
else
{
ShootSingleBulletOnMouseClick();
}
}
protected void MoveWeaponWithCamera(Transform weaponTransform)
{
_weaponTransform.rotation = _mainCamera.transform.rotation; //temporary way of making sure the gun moves with the camera
}
protected void ShootSingleBulletOnMouseClick()
{
if (Input.GetKeyDown(KeyCode.Mouse0)) //if left mouse button is clicked
{
CastRay();
}
}
protected void ShootRapidFireOnMouseHold()
{
if (Input.GetKey(KeyCode.Mouse0)) //if left mouse button is held down
{
CastRay(); //rapid fire
//PlayShootAnimation();
}
}
protected void CastRay()
{
RaycastHit hit;
if (Physics.Raycast(_mainCamera.transform.position, _mainCamera.transform.forward, out hit, 100))
{
Debug.Log(hit.transform.name);
}
}
//protected abstract void PlayShootAnimation();
}
I have a second script called MachineGun.cs, which inherits from Weapon.cs, and thus indirectly from scriptable object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MachineGun : Weapon{
private GameObject _machineGunBarrel;
//private float _animationVelocity;
// Use this for initialization
void Start () {
//initialize the basic properties of a weapon
_damage = 7;
_fireRatePerSecond = 10;
_isAutomaticWeapon = true;
_weaponTransform = GameObject.Find("Weapon_MachineGun").transform;
_machineGunBarrel = GameObject.Find("machinegun_p1");
_mainCamera = Camera.main;
}
// Update is called once per frame
void Update () {
MoveWeaponWithCamera(_weaponTransform);
FireWeapon();
}
//protected override void Shoot()
//{
// RaycastHit hit;
// if(Physics.Raycast(_mainCamera.transform.position, _mainCamera.transform.forward, out hit, 100))
// {
// Debug.Log(hit.transform.name);
// }
//}
void PlayShootAnimation()
{
_machineGunBarrel.transform.RotateAround(_machineGunBarrel.transform.up, _fireRatePerSecond * Time.deltaTime);
PlayShootAnimation(); //play the shoot animation of the gun
}
}
It's currently impossible, since MachineGun.cs doesn't inherit from monobehaviour anymore.
I have a weapon gameobject in my scene, and so here's my question:
How do I go about adding the MachineGun.cs script as a component to my weapon gameobject? Or since this is impossible,
How should I build a weapon system with a general Weapon.cs script from which all weapons can inherit basic functions and fields/variables?
EDIT 1: provided code to my post and added the "why I wanna do this".
Thanks in advance!
ScriptableObjects should mainly be used in a data oriented way, they are very convenient and quite efficient for the task. Also, plaguing your project of MonoBehaviour is a very bad (and wide-spread) practice.
IMO, you should have a MonoBehaviour with weapon logic management and your ScriptableObjects should be your weapon data (which is interpreted by loading them up in your Weapon MonoBehaviour), such that you have Minigun, Glock, Katana.. Scriptable objects which have data like, attack speed, reload speed, charger size, weapon model/textures, reference to model hitbox, yadayadayada. (You might have a generic Weapon MonoBehaviour, but derive a Gun one, a Blade etc.. for very specific management, but which will still need data from ScriptableObjects)
In short your MonoBehaviours define usage and interaction, while your ScriptableObjects define characteristics
ScriptableObject says:
Description
A class you can derive from if you want to create objects that don't
need to be attached to game objects.
So either you should not attach this script to game object or derive your object from "MonoBehaviour" if you want to attach it to game object.
Why did you derive it from ScriptableObject ?

how do I extend a class from another script in c# Unity

I'm trying to extend a base class on my player object.
player has damage script that looks like this
using UnityEngine;
using System.Collections;
public class Damage : MonoBehaviour
{
public int health = 100;
public virtual void ReceiveDamage(int damageAmount)
{
Debug.Log ("Original");
}
}
And then the same player has another script like this :
using UnityEngine;
using System.Collections;
public class playerDamage : Damage
{
public override void ReceiveDamage(int damageAmount)
{
Debug.Log ("Extended");
}
}
But when I call the script from a 3rd scrip on another object like this:
var damageScript = collision.gameObject.GetComponent<Damage>();
if( damageScript)
{
damageScript.ReceiveDamage(damageAmount);
}
the only response to the log is "Original"
Shouldn't the child be called and "Extended" written to the log?
There are several ways to do this. The easiest one is to SendMessage.
collision.gameObject.SendMessage("ReceiveDamage", damageAmount);
Whatever implementation of ReceivedDamage that the collision GameObject has, that will be the one that is called. This is awesome because you don't need to specify the type yourself nor use GetComponent.
Important Extra Information
In any implementation that you choose the key step is to make sure that the right script is attached to the collision.gameObject. If you attach both scripts then you are playing with fire.
To avoid playing with fire please make Damage an abstract class.
public abstract class Damage : MonoBehaviour
{
public int health = 100;
public virtual void ReceiveDamage(int damageAmount)
{
Debug.Log ("Original");
}
}
Abstract will give you the same functionality you want, except that Unity3d won't let you attach Damage to the GameObjects, which is good to avoid mistakes. You will always have the option to have the original ReceiveDamage and the choice to override it on future classes that inherit from Damage, like this:
public class Example : Damage
{
// This one still has ReceiveDamage but it happens in the base class.
}
or
public class PlayerDamage : Damage
{
public override void ReceiveDamage(int damageAmount)
{
Debug.Log("Extended");
}
}
I think when you call this line:
var damageScript = collision.gameObject.GetComponent<Damage>();
It gets the component by name, which in this case would be the Damage script, not the playerDamage script.
In you script it should be:
var damageScript = collision.gameObject.GetComponent<playerDamage>();
if( damageScript)
{
damageScript.ReceiveDamage(damageAmount);
}

Categories