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

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.

Related

Right way to set abstract class variable value

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.

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
}

C# Access to subclass Proprieties from base class instance

I don't really know how to formulate my issue it's a bit complicated for me, i'll try my best to explain.
I'm making a space game, i have a base class which represent places, and i want to have different type of places like planets, space stations, asteroïds, trading ships etc. The player can click on those objects and get informations.
So my classes looks like something like this:
public class Place {
public int placeId;
public string placeName;
public string placeDescription;
/* Place constructor */
}
public class Planet : Place {
/* Specific proprieties of planet */
public PlanetType planetType;
public int planetSize;
...
// Planet constructor
public Planet(int placeId, string placeName, string placeDescription, PlanetType planetType, int planetSize) : base(placeId, placeName, placeDescription) {
this.planetType = planetType;
this.planetSize = planetSize;
...
}
}
And i have a delegate which accept a function like selectPlace with Place in parameters because i don't want to make a delegate for each type of Place i have.
In another script which is supposed to show the information of any kind of Place, i recieves the Place object that the player clicked on. I think i found a solution, however is this correct to do something like this ?
private void updateSelectedPlaceUI(object sender, EventsController.PlaceEventArgs placeArgs){
// This is just a test, i should check which type of subclass it is before
Planet planetTest = placeArgs.Place as Planet; // So now i can use planetTest.planetType
}
And placing this in a switch case so i can handle any type. I just want to be able to get the proprieties from any derived class of Place in order to display them in UI. I would like to know a better way to achieve this.
But i'm wondering if my design is ok and necessary, it has been a while since i haven't used inheritance / polymorphism, and i feel like i'm doing it the wrong way.
I would propably make the UI part of showing the properties a specific place generic to accept something like a PropertyItem, you can decide the properties yourself.
public class PropertyItem
{
public string Text { get; set; }
public object Value { get; set; }
}
And then in your select method you would just call the abstract method of your base class (make your base class abstract as well)
public abstract class Place
{
...
public abstract IEnumerable<PropertyItem> GetProperties();
}
And now you can override this in your Planet
public class Planet : Place
{
...
public override IEnumerable<PropertyItem> GetProperties()
{
yield return new PropertyItem { Text = "Size", Value = this.planetSize };
}
}
And eventually you would use the GetProperties() method to get the properties of your place and show them in a tabular or what ever format your UI knows how to handle the PropertyItem type.
private void updateSelectedPlaceUI(object sender, EventsController.PlaceEventArgs placeArgs)
{
MyUserInterfaceWidget.DisplayProperties(placeArgs.Place.GetProperties());
}

Unity/C#: Class specialization by value (equivalent to C++ std::array<int>)

I have a problem that I know how to solve in C++, but I don't know how this works in C#. Here is a sample code in C#:
public class TowerTile<ScriptType, SpriteName> : Tile where ScriptType : MonoBehaviour {
private void Awake() {
gameObject = new GameObject();
gameObject.AddComponent<ScriptType>();
colliderType = Tile.ColliderType.None;
sprite = GameManager.shared.sprites[SpriteName];
}
}
The SpriteName from the example should be of type string so I could use it in operator[] on Dictionary<string, Sprite> sprites.
Here is how I would write it in C++:
template <typename ScriptType, typename SpriteRef = std::string>
class TowerTile : public Tile {
private void Awake() {
gameObject = new GameObject();
gameObject.AddComponent<ScriptType>();
colliderType = Tile.ColliderType.None;
sprite = GameManager.shared.sprites[SpriteRef];
}
}
How can I achieve this in C#? I can't pass the string in as a constructor argument because these Tile objects are instantiated with ScriptableObject.CreateInstance<T>(), which can't pass any constructor arguments.
To make the question and answer more clear, I wanted to template this class from example because the way it was instantiated in Unity with ScriptableObject.CreateInstance<T>() didn't allow for constructor arguments. Even if the specialization by value was possible in C# (which is not), it wouldn't help either because the instatiating method can't create generic types anyway (why Unity?). So my solution to I've used is a factory with a templated method that returns base class 'Tile'.
public class TileFactory {
private TileFactory() {}
public static Tile CreateInstance<ScriptType>(string spriteName) where ScriptType : DefaultAI {
var tile = ScriptableObject.CreateInstance<TowerTile>();
tile.sprite = GameManager.shared.sprites[spriteName];
tile.gameObject.name = spriteName;
tile.gameObject.GetOrAddComponent<ScriptType>();
return tile;
}
}
Changes from the example in question are:
Problem of not being able to pass spriteName into ScriptableObject.CreateInstance<T>() is solved by wrapping this method in my own factory method.
ScriptableObject.CreateInstance<T>() is called with non-generic type and only after is added the generic type script to the object.
Returned instance is also a non-generic type which as it seems Unity has some problems with for some reason.
ScriptType now should be of base DefaultAI which is of base MonoBehaviour (not really relevant for the purposes of this answer).
Another option would be to use a Buidler but since I only needed to pass 1 required argument, builder wouldn't make much sense. In some other cases though it is would be a better solution.
p.s.: Thanks to Furkan Kambay for providing relevant sources that helped solve this issue

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.

Categories