Right way to set abstract class variable value - c#

I have camera script class that do culling task and it contains these variables and an event :
protected float CullDetailSmall = 25.0f;
protected float CullDetailMedium = 80.0f;
protected float CullDetailLarge = 130.0f;
protected float CullDetailExtraLarge = 250.0f;
protected float CullDetailXExtraLarge = 450.0f;
protected float CullDetailXXExtaLarge = 650.0f;
public virtual void Awake(){
//culling apply logic using above variable values
}
The camera script class is the base class for CamFly and CamWalk. Now i want to change the base class camera script variable values, so I make this function in each class (CamFly and CamWalk)
public void SetCullingValues(int cullDetailSmall
, int cullDetailMedium
, int cullDetailLarge
, int cullDetailExtraLarge
, int cullDetailXExtraLarge
, int cullDetailXXExtaLarge
, int CullFloor
)
{
base.CullDetailSmall = cullDetailSmall;
base.CullDetailMedium = cullDetailMedium;
base.CullDetailLarge = cullDetailLarge;
base.CullDetailExtraLarge = cullDetailExtraLarge;
base.CullDetailXExtraLarge = cullDetailXExtraLarge;
base.CullDetailXXExtaLarge = cullDetailXXExtaLarge;
base.CullFloor = CullFloor;
base.Awake();
}
It is working fine and doing what i want but its certainly not a good piece of code. I am amzed that how can i do it correctly?? Remember
i am calling above function under some conditions, like if some
condition are matched then execute above function and change base
class variable.
second i want to this for both inherited members.

Please check the next link from Microsoft with relevant abstract class documentation and best practices.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract
An abstract class is used as a base template for derived classes. It is used to enforce a design rule.
abstract class YourClass
{
public int a;
public abstract void A();
}
class Example : YourClass
{
public override void A()
{
Console.WriteLine("Example.A");
base.a++;
}
}

have Camera script class that do culling task and it contains these variables and an event
I see no event, I see a void returning virtual method named Awake.
It is working fine and doing what i want but its certainly not a good piece of code
What makes you think that? Yes, its improvable but I've seen far worse.
I am amzed that how can i do it correctly
Yeah, that happens to all of use sometimes...
My two cents of advice:
In general, do not expose fields directly. If the fields are subject to modification, use read/write properties. This way you can always ensure that the state of your base class remains consistent.
Name methods appropiately so the name conveys what the method does. SetCullingValues does not make it clear that the method will also call Awake. Either call it SetCullingValuesAndAwake or do not call Awake.
Why is SetCullingValues even declared in the derived types? Declare it in the base type.
1 and 3 assumes you have access to Camera. If you don't then there is not much you can do to improve what you already have.

Related

Unity 2D - access to variables of GetComponent<T> sent as param

I have this script for saving my character. I have lot of NPCs and each NPC has its own script. I want to use method SaveCharacter for saving every NPC. How can I send g.GetComponent<Warrior_Movement>() as a parameter and get access to its variables? When I use <T>, I can't get variables of gameobject's script. Thanks
void SaveCharacter(Character character, string gameObjectName)
{
GameObject g = GameObject.Find(gameObjectName);
character.position = new float[] { g.transform.position.x, g.transform.position.y, g.transform.position.z };
character.selectedScheme = g.GetComponent<Warrior_Movement>().selectedScheme;
character.currentWaypointIndex = g.GetComponent<Warrior_Movement>().currentWaypointIndex;
character.nextWaypointIndex = g.GetComponent<Warrior_Movement>().nextWaypointIndex;
character.loopSide = g.GetComponent<Warrior_Movement>().loopSide;
}
Problem
You can NOT use a variable for passing it into the generic version of GetComponemt like
public void DoSomething(Type componemtType)
{
var component = GetComponent<componentType>();
}
since the types used for implementing the generics have to be compile time constant and therefor can not be a variable (or only one that is compile time constant)!
You could use the non-generic version of GetComponent like
public void DoSomething(Type componentType)
{
var component = GetComponent(componentType);
}
but than you would have to parse it in order to have access to any fields or methods that are specific for that component:
var parsedComponent = component as componentType;
So again you can NOT do this since the type used for as also has to be compile time constant and therefor can not be a variable.
Solution 1: Shared Base class
In the case all your different classes have common fields you want to save, they should all inherit from a common base type like e.g.
// By making a class abstract it can not be instanced itself but only be implemented by subclasses
public abstract class BaseMovement : MonoBehaviour
{
// fields that will be inherited by subclasses
public int currentWaypointIndex;
public int nextWaypointIndex;
//...
// You could also have some generic methods that are implemented
// only in the subclasses like e.g. Getters for the values
// you want to access
public abstract string SaySomething();
}
And than inherit your classes from that like e.g.
public class Warrior_Movement : BaseMovement
{
// Inherits all fields of BaseMovement
//... additional type specific stuff
// If you have abstract methods in the base class
// every subclass has to implement all of them
public override string SaySomething()
{
return "Harr harr, I'm a warrior!";
}
}
public class Other_Movement : BaseMovement
{
// Inherits all fields of BaseMovement
// Additional type specific stuff
// If you have abstract methods in the base class
// every subclass has to implement all of them
public override string SaySomething ()
{
return "Harr harr, I'm ... something else :'D ";
}
}
Than you can use something like
void SaveCharacter(Character character, BaseMovent bMovement)
{
character.position = new float[] { bMovement.transform.position.x, bMovement.transform.position.y, bMovement.transform.position.z };
character.currentWaypointIndex = bMovement.currentWaypointIndex;
character.nextWaypointIndex = bMovement.nextWaypointIndex;
}
and call it like
GameObject g = GameObject.Find(gameObjectName);
var movement = g.GetComponemt<BaseMovement>();
SaveCharacter(someCharacter, movement);
Solution 2: Overloads
Alternatively if you use different values for different components than don't use a shared base class but instead create overloads of SaveCharacter like
public void SaveCharacter(Character character, Warrior_Movement wMovement)
{
// Do stuff with wMovement
// ...
}
public void SaveCharacter (Character character, Other_Movement oMovement)
{
// Do stuff with oMovement
// ...
}
so the wheneever you use SaveCharacter the types you pass in will decide which implementation of SaveCharacter should be used.
To
When I use , I can't get variables of gameobject's script.
I don't know what you mean exactly but you can access the GameObject the component is attached to by using gameObject e.g.
bMovement.gameObject.SetActive(true);
Note:
If somehow possible you should avoid using Find since it is quite performance intense. Try to get the reference as soon as possible e.g. either by referencing it in the Inspector using a public GameObject field or if it is Instantiated save it to a variable like
var warrior = Instantiate (/*...*/);
and pass it around.

Can I use an abstract base class as a Unity Editor element?

I'm trying to create a component for a Unity GameObject, let's call it MediaController. I want it to be able to manage timing (play/pause/etc) for different media (audio/video). I created an abstract class PlayableMedia with basic properties/fields/methods and created 2 classes, PlayableVideo and PlayableAudio, that inherit and implement according to our needs.
The intent was to have a singular list of PlayableMedia that could be audio/video agnostic, allowing an easy (i.e.) media.Play() call regardless of type at specific app times... but my field public List<PlayableMedia> MediaList; is not appearing in the editor and there is no error.
So ultimately my question is as the title states: is it possible to use the PlayableMedia class as the type of a field?
I'm suspecting "no" based on my experiences with this, but I've found links that say "yes" or "yes, sort of" that seem to point to custom editors/inspectors/drawers, but I have 0 experience with those and haven't been able to get it implemented (see below).
[System.Serializable]
public class RegisteredMedia
{
public float StartTime;
public PlayableMedia Media;
}
[CustomPropertyDrawer(typeof(RegisteredMedia))]
class RegisteredMediaDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Playable Media"));
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect rectStartTime = new Rect(position.x, position.y, 30, position.height);
Rect rectMedia = new Rect(position.x + 35, position.y, 50, position.height);
EditorGUI.PropertyField(rectStartTime, property.FindPropertyRelative("StartTime"), GUIContent.none);
EditorGUI.PropertyField(rectMedia, property.FindPropertyRelative("Media"), GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
public class MediaController : MonoBehaviour
{
public List<RegisteredMedia> MediaList = new List<RegisteredMedia>();
\\[...] rest of implementation
}
Can anyone help me out? Either confirm that it isn't possible, or help me with an implementation if it is?
Also, if it can be done with custom editors/inspectors/drawers, can someone help me get a single item in the List<RegisteredMedia> to display as Start Time ____ Playable Media [=====] (where PlayableMedia will be a GameObject with the proper component attached)?
Be careful of your use of the word "property". In C# it means something very specific.
is it possible to use the PlayableMedia class as the type of a property?
I think you are asking the wrong question here. Rather than coming up with a new implementation, consider why your current implementation might not be working?
Firstly, I'll give you the following example:
public abstract class Car : MonoBehaviour { }
public class Jeep : Car { }
public class Ferrari : Car { }
public class CarHolder : MonoBehaviour
{
public List<Car> Cars;
}
In this example, I could create a GameObject with the CarHolder component, and was able to attach both Jeep and Ferrari Objects. It is important to note that each monoBehavior class I defined sits in its own file and the file name matches the class name. This is just how Unity works.
So to answer the question I think you are asking (assuming we replace "property" with "field"), it is indeed possible to use abstract class types and have them show up in the inspector. I suspect that you need to separate your classes into separate files.
It's possible natively since 2019.3 release via [SerializeReference] attribute https://docs.unity3d.com/ScriptReference/SerializeReference.html
e.g.
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public abstract class AbstractExample {
public int foo;
}
// [Serializable] not needed here
public class ConcreteExample : AbstractExample {
}
public class Consumer : MonoBehaviour {
[SerializeReference]
public List<AbstractExample> examples = new() { new ConcreteExample() };
// both the list and the concrete instance visible in the editor
// and editable without any additional editor extensions
// note that you can't effectively add new items to the list via editor
// since that way you create a faulty abstract-ish instances instead
// (no actual way for the editor to know what subtype do you want)
// if you're OK with having the base class being not explicitly abstract
// and can provide a sensible constructor for it, just drop the abstract
// you'll still have full polymorphism support etc. with SerializeReference
}

Unity - Baseclass should get Information from Subclass

I am trying to make use of the MVC Pattern in Unity. I am a programming beginner.
Traps and moving Platforms use the same code so i created a base for them. I divide the code into "Data"-class and "Method"-class.
Both objects move to Point A, then to Point B, back to Point A and so on..
Point A and Point B got a trigger, to change the Movementdirection of the Platform/Trap.
The base class holds the data. The subclass gets the data and fills the base data. In the base class i have the object:
public virtual GameObject MovingObject { get { return null; } }
The subclass overrides the property returning null to make it return the right object. I try it this way:
[SerializeField]
private GameObject movingObject;
public override GameObject MovingObject { get { return movingObject; } }
The private variable is set in the Editor and sets the value to the property. This property gives the information to the base class. The problem is that i get null references and I do not know how to fix that. The base class does not return an object. The information get lost on their way to the base...
Is my logic wrong?
If you need to see the whole structure of these six classes you can look it up on
https://github.com/Garzec/MidnightFeast/tree/master/Assets/Scripts/MovingObjects
Sorry, i did not want to post all lines of code and unrelevant stuff :)
I looked at your code. Assuming you will never need an instance of just a "MovingObjectsController" this looks like you need an abstract class as your base class. An abstract class cannot be instantiated, but it can require a child class (subclass) to implement abstract members, removing the need to return null in the parent. For example, you would define your controller as :
public abstract class MovingObjectsController
{
protected abstract MovingObjectsData Data { get; }
}
public class PlatformController : MovingObjectsController
{
private MovingObjectsData data;
public PlatformController()
{
this.data = new MovingObjectsData(); //This being whatever data is specific to this object
}
protected override MovingObjectsData Data {
get
{
return data;
}
}
}
This way the child is required to implement whatever the parent needs, but the parent isn't required to provide a default implementation that doesn't make sense.

Using constructors of monobehaviors

OK, I know why we don't use constructors on monobehaviors in Unity. For nearly all of the use cases, Start and Awake fit perfectly. Usually.
However, there's a great C# feature that only can be used with constructors — readonly fields. In my particular situation, I work on a project with a lot of developers and write an abstract MonoBehavior that will be subclassed and rewritten a lot of times by a lot of different people. And I want a field to act like constant throughout object's lifetime (or it WILL introduce strange, hard-detectable bugs) but with different values in different subclasses — in other words, a classic use-case for a readonly field. (I don't want to use properties because they have no language-enforce obligation of staying the same.)
So — can I safely use MonoBehavior's constructors? Won't some strange dragon come out of the lair somewhere down the road? What should I know if I choose to use them?
I think the main reasons Unity wants you to stay away from using the constructor is that the constructor isn't called on the main thread, and the constructor is called before serialized data is restored to the object.
So if the readonly fields you're setting in the constructor depend on data from serialized fields, then they won't work right. And if they don't then you can just assign them at initialization.
You could also use a container object to keep your readonly values, but there's nothing stopping someone else from re-assigning that container later.
using UnityEngine;
using System.Collections;
public class ReadOnlyTest : MonoBehaviour {
public string part1 = "alpha"; // change these values in the editor and
public string part2 = "beta"; // see the output of the readonly variable "combined"
public readonly string combined;
// just assign to readonly vars.
public readonly string guid = System.Guid.NewGuid().ToString();
public readonly float readOnlyFloat = 2.0f;
public class ReadOnlyContainer {
public readonly int readOnlyInt;
public readonly float readOnlyFloat;
public readonly string readOnlyString;
public ReadOnlyContainer(int _int, float _flt, string _str) {
readOnlyInt = _int;
readOnlyFloat = _flt;
readOnlyString = _str;
}
public override string ToString() {
return string.Format("int:{0} float:{1} string:{2}", readOnlyInt, readOnlyFloat, readOnlyString);
}
}
public ReadOnlyTest() {
combined = part1 + part2;
}
public ReadOnlyContainer container;
void Awake() {
if (container == null) {
container = new ReadOnlyContainer(Random.Range(-100,100), Time.realtimeSinceStartup, System.Guid.NewGuid().ToString());
}
}
void Start () {
Debug.Log(container.ToString());
Debug.Log("combine1: " + combined);
Debug.Log("guid: " + guid);
}
}
Many unity classes are created by reflection, and there's no way for unity to non-default constructors properly; hence the limitation.
#Calvin's answer points out one very good option: create classes that are not derived from MonoBehaviour; these can have constructors like any other C#. You can put those classes into fields in MonoBehaviours as long as your code can tolerate missing instances. If you use the typical quasi-singleton pattern from #Calvin's answer you'll always get an instance when you need one, and you can push the 'give me an instance the first time' logic into a method that can be overridden in derived classes to customize behavior.
If you want constant-like behavior, with the option of different values in derived classes it may be easier to define a method rather than a field. The method is effectively read-only, and it has more predictable mutations as per #Jerdak's answer.
If you must have constructors, the last option is to use the monobehavior as a minimal placeholder and write all of the interesting stuff in a class of your own, then delegate all of the work in the Monobehavior to your class.
using UnityEngine;
using System.Collections;
public class OuterPlaceholder: MonoBehaviour {
public InnerBehavior _Inner;
public void Awake() {
if (_Inner == null) {
_Inner= new InnerBehavior(4);
}
}
public void Update()
{
_Inner.DoUpdate(this);
}
}
public class InnerBehavior
{
public readonly int UpConstant;
public InnerBehavior (int up)
{
UpConstant = up;
}
public void DoUpdate(MonoBehaviour owner)
{
owner.transform.Translate(Vector3.up * UpConstant * Time.deltaTime);
}
}
This option may work best if you are sure you're going to get a lot of complex inheritance as the project evolves.
Finally: It's perfectly OK to name the field _ReadOnlyField or _DoNotWrite or whatever to tell users not to muck with it. All Python programmers live with the possibility of somebody doing far worse things and it seems to work out fine most of the time :)
From the script refs:
If you attempt to define a constructor for a script component, it will
interfere with the normal operation of Unity and can cause major
problems with the project.
MonoBehaviours are constructed many times during serialization, something Unity does quite frequently in the editor and I suspect there is a lot more going on the hood to hook the C layer to C#. Ultimately the behavior is undefined so it's best not to try.
Regarding "but with different values in different subclasses", from MSDN:
assignments to the fields introduced by the declaration [readonly] can only occur as part of the declaration or in a constructor in the same class.
So no modification in derived classes.

C# Base or Template Class?

Ok so I am making a game using XNA, I would like all of the enemies to extend from one base class called "baseEnemy.cs". For example, I would like a zombie to have a class called "zombie.cs" but make it entend the "baseEnemy.cs".
I think I remember being told its would be laid out like:
class zombie : baseEnemy
{
}
But I am assuming the use of get{} and set{} would help me to change values of current variables in zombies.cs that exist as part of baseEnemy.cs... If that makes sense? I don't understand the usage of get{} and set{} but I have seen it in other languages (such as the code for minecraft) which I would assume are similar in their working.
So, say I have a float for the speed of the enemy... I don't want all the enemies to move at the same speed (zombie's should have a low speed, etc). How could I get the speed from the baseEnemy.cs and set it as the speed in zombie.cs.
Or would I be better just making the variables public in baseEnemy.cs?
Sorry if the above doesn't make much sense, I am not too experienced with XNA or terminology used and therefore I probably sound like I am going round in circles :S
You are looking for so called abstract methods or abstract properties.
abstract class Enemy
{
public abstract float GetSpeed();
}
class Zombie : Enemy
{
public override float GetSpeed()
{
return 10;
}
}
Note the abstract keyword preceding the class name and the method name. The child class has to implement all abstract members, if it is not abstract itself. When an abstract member is implemented the override keyword must be used.
The get set syntax you are describing is called a property. It is a special C# construct that organizes the getter and/or setter of a field and puts them in a single block. The same example as above using properties:
abstract class Enemy
{
public abstract float Speed { get; }
}
class Zombie : Enemy
{
public override float Speed
{
get { return 10; }
}
}

Categories