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
Related
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.
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
}
So basically I want to end with several ingame objects, each having few .cs scripts with monobehaviour. (different set of scripts on each object)
party of characters with their skills
Those scripts on each character can vary over time
characters learning new skills/abandoning old
So when the game starts, I want to attach the scripts to objects dynamically at runtime
based on player decision in skill tree
Is there any way how can I do this without using reflection?
EDIT: It seems I found solution how to make it work without reflection
public class TestSkill : MonoBehaviour {}
public class TestFireball : TestSkill {}
public class TestMeleeAttack : TestSkill {}
public class TestSkillBook : MonoBehaviour {
public MonoScript script;
void Start () {
System.Type t = script.GetClass();
TestSkill skill = gameObject.AddComponent(t) as TestSkill;
}
}
I want to attach the scripts to objects dynamically at run-time.
he AddComponent function is used to attach scripts to GameObjects.
Your Object
public GameObject yourgameObject;
Attach script to it:
yourgameObject.AddComponent<YourScript>();
EDIT:
The problem is, I dont know if it will be MyScript1 or MyScript2. I
dont want it to be hardcoded, but modifiable via editor/ingame UI.
I think that you are looking for AddComponent that can take string as param.
There used to be one like this:
public Component AddComponent(string className);
but it was deprecated years ago. I made a new one called AddComponentExt as extension method last year and you can get it here. It can be used like this:
yourgameObject.AddComponentExt<"YourScript">();
You can add script even if it doesn't exist yet. You will get run-time error instead of compile-time error in that case.
Is there any way how can I do this without using reflection?
No, you can't do this without reflection since it doesn't exist yet. That's what reflection is used for.
Since this doesn't fit into the comments of Programmers answer, some example and to clarify on what you can/must do:
// The component to add
public class B : MonoBehaviour
{
public void TestCall()
{
Debug.Log("Success");
}
}
public class A : MonoBehaviour
{
public string ToAddName = "B";
private void Start()
{
System.Type t = System.Type.GetType(ToAddName);
AddComponent(t); // This does successfully add the component (assuming it exists)
Debug.Log(t.GetType()); // This will give out "System.MonoType"
// This doesn't work since it is not known that this is actually "B", not "MonoScript"
GetComponent(t).TestCall();
// What is possible is this, requires hardcoding the method name though:
System.Reflection.MethodInfo mI = t.GetMethod("TestCall");
var c = GetComponent(t);
mI.Invoke(c, null); // null because "TestCall" doesn't take params
}
}
This is not meant to be an actual solution, I'd rather say that there is probably a (better) way to set up your whole construct so that you don't have this problem at all.
I am making a game engine with the MonoGame Framework. Since this is a multiplayer game, other players need to know certain information about other players, such as position and velocity, typically for AI reaction.
I am about to implement Lua scripting for player actions. However, I can't currently think of a way to have it so a player cannot modify the other players' information, such as position and name.
For instance, say I have the following property in C#:
public Vector2 Pos { get { return pos; } private set { this.pos = value; } }
private Vector2 pos;
Looks good, right? Well not quite. Say I have a function that returns the closest enemy called GetNearestEnemy(), which returns another Player. Because the Player class has access to its own properties, I can just as easily do something like this in some other method:
public void DoStuff()
{
Player otherPlayer = GetNearestEnemy();
otherPlayer.Pos = new Vector2(34,151);
}
So I think "maybe I could just pass the instance as a parameter."
public void SetPos(Player instance, Vector2 pos)
{
if (instance != this)
return;
else
this.pos = pos;
}
... and always pass "this" as an argument. However, that also has the same problem: I could just as easily pass GetNearestEnemy() to the method and I'd be able to set the enemy position.
This is the type of behavior I am trying to prevent. Is there a way to preferably either:
Get the instance that called the method?
Implicitly set a parameter from the calling object without them knowing?
Or is there some other way to approach it that I'm not seeing?
Unfortunately not.
There is no language support for what you want.
Let me be specific about what you want just so that you understand what I answered.
Your question is basically this: Given that I have two instances of an object, and I have properties in this object that have a private setter, is there any language support for ensuring that instance #1 cannot change this private information of instance #2?
And the answer is no.
This will compile and "work":
public class Test
{
public void TestIt(Test t)
{
t.Value = 42;
}
public int Value
{
get;
private set;
}
}
...
var t1 = new Test();
var t2 = new Test();
t1.TestIt(t2); // will "happily" change t2.Value
Basically, the onus is on you to make sure this doesn't happen if you don't want it to happen. There is no language or runtime support to prevent this.
The access modifiers you can use are:
public: Anyone can access this
private: Only the type can access this
protected: Only the type, or a descendant of the type, can access this
internal: Any type in the same assembly can access this
internal protected: Any type in the same assembly or a descendant, can access this
Other than this, you have no other options. So "only the same instance can access this" does not exist as an access modifier.
If this is the property and you are worried about being set from elsewhere:
public Vector2 Pos { get { return pos; } private set { this.pos = value; } }
private Vector2 pos;
This will NOT work. So you do not need to worry:
Player otherPlayer = GetNearestEnemy();
otherPlayer.Pos = new Vector2(34,151); // <--- no this will not work
That property can only be set from within the class.
That won't work. If Pos is a property with a private setter (as it is) the only way they could change it would be by calling a public method from within otherPlayer. Something like otherPlayer.SetPos(new Vector2(34,151)), where SetPos() is:
public void SetPos(Vector2 NewPos)
{ Pos = NewPos; }
The original scenario posted can be handled using an interface e.g. IPeerPlayer that only exposes what other players should see and hides other properties (i.e. the other properties would not be in the IPeerPlayer interface.)
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.