I'm trying to create an RTS on UnityEngine. Like the age of empire series there is a menu at the bottom of screen that depends of the object that you select.
Therefore, I created a class SObject (for selectable object) from which all objects inherit. For example, there is a class SHarvester, a class SAnimals, etc... The class SObject has an attribute :
[SerializeField]
private List<UnityAction> m_buttonCreation = new List<UnityAction>();
This list contains the methods specific to the object which will be linked to the buttons of the menu at the bottom of the screen.
Everything is working well if you defined the list from the script.
But I would like to have prefabs of unit or building in the editor and to define the list of method directly from the inspector. But the unityAction types doesn't displayed in inspector.
Is there a way of displaying the UnityAction type in inspector ? Something like the button component :
I apologize if I didn't make myself clear.
If you are not sticking to a UnityAction you could just use:
[SerializeField]
List<UnityEvent> m_buttonCreation = new List<UnityEvent>();
Which will show up like this in your inspector
The default UnityButton component has UnityEvents and not UnityActions anyway:
Button.cs
namespace UnityEngine.UI
{
[AddComponentMenu("UI/Button", 30)]
public class Button : Selectable, IPointerClickHandler, IEventSystemHandler, ISubmitHandler
{
protected Button();
public ButtonClickedEvent onClick { get; set; }
public virtual void OnPointerClick(PointerEventData eventData);
public virtual void OnSubmit(BaseEventData eventData);
public class ButtonClickedEvent : UnityEvent
{
public ButtonClickedEvent();
}
}
}
and ofcourse the UnityEvent works with a UnityAction so IMHO you should just change Action to Event.
UnityEvent.cs
public class UnityEvent : UnityEventBase
{
[RequiredByNativeCode]
public UnityEvent();
// This is what runs when you click on the (+) sign
public void AddListener(UnityAction call);
public void Invoke();
// This is what runs when you click on the (-) sign
public void RemoveListener(UnityAction call);
protected override MethodInfo FindMethod_Impl(string name, object targetObj);
}
Related
I need to serialize a class in a custom inspector (using visually the Editor) like as doing in a Monobehaviour script like this:
[System.Serializable]
public class CustomClass
{
int myInt
}
public class OtherClass : MonoBehaviour
{
[SerializeField] CustomClass customClass;
}
which gives this result:
result wanted and given using the code above, where DamageEffect = CustomClass and Damage = myInt
In my custom editor, I'd like something like this:
[CustomEditor(typeof(CardObject))]
class AnotherClassEditor : Editor
{
public override void OnInspectorGUI()
{
[SerializeField] CustomClass customclass;
}
}
but, as expected, it points out an error.
I also tried with EditorGUILayout.ObjectField() but I haven't been able to, I'm not so experienced so please try to keep the answers simple.
Actually, I need this serialization to happen only when an enum is equal to a certain value, the overall script is something like this:
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(fileName = "Card", menuName = "CardObject")]
public class CardObject : ScriptableObject
{
public List<CardEffectType> effectsTypes;
//other...
[HideInInspector] public List<CardEffect> effects;
//other...
}
#if UNITY_EDITOR
[CustomEditor(typeof(CardObject))]
class CardObjectEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
CardObject cardObject = (CardObject)target;
foreach(CardEffectType effectType in cardObject.effectsTypes)
{
switch (effectType)
{
case CardEffectType.DamageEffect:
{
//!!!
}
}
}
}
}
#endif
public enum CardEffectType
{
DamageEffect, //other...
}
I found some workarounds but the result is not as when a class is serialized in a Monobehaviour.
When you just want to show something like how Unity would, you can use EditorGUILayout.PropertyField(), but it asks for a SerializedProperty, what you need to get from the SerializedObject, not from the actual target. Something like this:
[CustomEditor(typeof(MyType))]
public class MyTypeEditor : Editor
{
private SerializedProperty _variableIWantToShow;
private void OnEnable()
{
_variableIWantToShow = serializedObject.FindProperty("<name-of-the-variable>");
}
public override void OnInspectorGUI()
{
// ...
if (ShowVariable) EditorGUILayout.PropertyField(_variableIWantToShow);
// ...
}
}
You can manage collections (array, list, etc.) as SerializedProperty, but it adds some complexity.
https://docs.unity3d.com/ScriptReference/SerializedProperty.html
https://answers.unity.com/questions/682932/using-generic-list-with-serializedproperty-inspect.html
Just make your int myInt public, or add [SerializeField] attribute to it - inspector is only designed to work with serialisable fields (public fields are serialised by default in unity editor), currently your myInt is private hence only visible from within
As mentioned in an answer by h4i, proper way to display objects in the editor is using SerializedProperty, casting 'target' seems like a good idea at start but is only useful if you want to call methods on the object, it will fail in several other cases.
What you may want to consider is declaring a PropertyDrawer while Editors service a single monobehaviour type, a PropertyDrawer handles displaying every instance of a serializable class, and will be used every time a default editor uses a PropertyField internally. This will probably match your use case better
This is in the context of creating an RTS, in which the aim is to have several Units (Buildings; Movers : Workers, Soldiers...) that one can select as one or a group. When selected, the units can be moved (Mover), and can have specific methods (Building: can create a Mover...).
I have (heavily) based the selection system on:
https://www.youtube.com/watch?v=vAVi04mzeKk
I have a class Unit, which is added upon Start within the unitList field of the class UnitSelections (this makes all Unit objects of the Scene accessible for selection):
public class Unit : MonoBehaviour
{
public int maxHealth;
public int health;
public bool isSelected = false;
public bool canMove = false;
public bool canCreateUnits = false;
public bool canAttack = false;
public bool canGatherResources = false;
void Start()
{
//add this unit to the list
UnitSelections.Instance.unitList.Add(this);
health = maxHealth;
}
void OnDestroy()
{
//remove it from the list when destroyerd
UnitSelections.Instance.unitList.Remove(this);
}
}
The class UnitSelections holds the data on all Unit objects in the Scene, and all Unit objects that are currently selected. It includes methods on how to select them (via left click, drag + left click, shift + left click...):
public class UnitSelections : MonoBehaviour
{
public List<Unit> unitList = new List<Unit>();
public List<Unit> unitsSelected = new List<Unit>();
private static UnitSelections _instance;
public static UnitSelections Instance { get { return _instance; }
//more code with methods to add units to the unitsSelected
}
And then a class Building, child of Unit, which has a method that can be triggered if the field isSelected is TRUE (create a Mover object). This is to say, when the Building is selected, only then can you trigger its methods via e.g. keyboard input:
public class Building : Unit
{
void Start()
{
canCreateUnits = true;
maxHealth = 1000;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.B) && this.isSelected)
{
CreateMover();
}
}
void CreateMover()
{
// behaviour here
}
}
In the unity editor, I have a prefab which corresponds to the Building subClass:
If I add as a component the script Building.cs to the GameObject Building, does it inherit everything from Unit.cs ?
Or does it need to have the Unit script as a component too ?
--> For fields and "normal" methods (not Start/Awake/Update), yes. So the GameObject Building only needs to have the Script Component Building
However, the Start method of Unit is not called in the Building class, which makes it not possible to add it to the unitList field of the class UnitSelections.
How can the Start method of Unit be called for an object with the Building Script Component ?
--> Need protected virtual void Start() for the TopClass, and can call base.Start() within the protected override void Start() of the ChildClass
See solution answer for details
Just as Unit inherits from MonoBehaviour, Building inherits Unit (which inherits MonoBehaviour). As such, you should ONLY add Building as a component to your GameObject. If you add both, you'll end up with two distinct components on your game object - the Building component AND a Unit component. Each component would have it's own set of variables, and listen and react to the Unity messages independently, for example Update() if it were present if both classes.
It's also interesting to note that MonoBehaviour inherits from Behaviour, which inherits from Component, which inherits from Object (UnityEngine.Object, not System.Object). So Building.cs in this case is just another subclass of a component and is treated as such. You can check out the lineage on the Unity docs pages.
This image demonstrates the problem, where each component has it's own set of field values.
Now, if we create a small test script to look at the GameObject:
We get this as the result:
As you can see, there are two "Unit" components on the HumanBarracks GameObject, a Unit and a Building which is also of type Unit.
Extra
public class Unit : MonoBehaviour
{
public int maxHealth;
public int health;
public bool isSelected = false;
public bool canMove = false;
public bool canCreateUnits = false;
public bool canAttack = false;
public bool canGatherResources = false;
// By making Start protected virtual, child classes will run it and can override it as well if they require a specific implementation.
protected virtual void Start()
{
Debug.Log("This Start method will now be called by all children.");
}
}
public class Building : Unit
{
protected override void Start()
{
// call the base Start method on Unit.
base.Start();
// Now add this building’s particular Start code.
Debug.Log (“Building specific Start code!”);
}
}
As you can see the building component has all fields of unit component. So yes it inherits everything. The building component is unit component + all inside (start, update, CreateMover). If you add building component and unit component then you have 2 unit components where one of them is extended with start, update and CreateMover.
TLDR: How can I have a script that inherits from a public abstract class have access to an often changing Enemy gameObject variable (so it can't be static) without passing it through several other scripts first?
In my game, I have a battle system where a different "Battle Event" gets loaded for each battle. Each "Battle Event" gets its own script, and each of those events inherits from the same BattleEvent parent (which is public abstract).
The code structure basically goes:
BattleSystem (main brain of battles which holds the Enemy
gameObject) ->
BattleEventsManager (handles both which BattleEvent to load, and which methods to run on that BattleEvent) ->
a random BattleEvent (BattleEventOne or BattleEventTwo etc)
public class BattleSystem : MonoBehaviour
{
BattleEventsManager battleEventsManager;
public Enemy currentEnemy;
// the Enemy data is passed when the battle starts
public void Start(Enemy enemyToLoad)
{
battleEventsManager = GetComponent<BattleEventsManager>();
currentEnemy = enemyToLoad;
}
public void BeginPlayerTurn()
{
battleEventsManager.SetupEvent(currentEnemy);
}
}
public class BattleEventsManager : MonoBehaviour
{
BattleEvent currentBattleEvent;
private void Awake()
{
// define this battleEvent
currentBattleEvent = GetComponent<BattleEventOne>();
}
public void SetupEvent(Enemy currentEnemy)
{
// start the battleEvent with its Setup function
currentBattleEvent.Setup(currentEnemy);
}
}
// inherits from `BattleEvent` parent class, shown below
public class BattleEventOne : BattleEvent
{
// override the method from the parent
public override void Setup(Enemy currentEnemy) {
// we can now use the data we need in `currentEnemy`
// all I wanted was to get access to `BattleSystem.currentEnemy`
// but i had to pass it down all the way here. Is there a better way?
}
}
// parent of all `BattleEvents`
public abstract class BattleEvent : MonoBehaviour
{
public abstract void Setup(Enemy currentEnemy);
} // end BattleEvent class
As you can see, the the currentEnemy variable needs to be passed down through 2 classes in order to get to where it needs to be: BattleEventOne.Setup().
Furthermore, I needed to add the Enemy currentEnemy param to the parent BattleEvent, which is problematic because not all BattleEvents will need this information.
I originally wanted to just call BattleSystem.currentEnemy from BattleEventOne (using a property or something), but because the BattleSystem is abstract/static, it can't access it. And because currentEnemy contains new data each battle, I can't make that a static variable.
So, how can I have BattleEventOne here access BattleSystem.currentEnemy without having to pass it down as I've done above?
(I still struggle a lot with passing information between scripts, so any help here is really appreciated!)
I'm pretty new to Unity and C# scripting.
In my scene I have four buttons. I declared them in my script as public and put the items (on type Button) via drag and drop in my inspector in unity. Depending on which button is clicked, i want my program to do something. You can see in the script below what I've done. That scripts works perfectly just like I want it to.
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class Example : MonoBehaviour
{
public Button Left;
public Button Right;
public Button Starten;
public Button Zurueck;
private void Start()
{
loadRezept(0);
Right.onClick.AddListener(iterationUp);
Left.onClick.AddListener(iterationDown);
Starten.onClick.AddListener(starten);
Zurueck.onClick.AddListener(zurueck);
}
... (here are the methods "iterationUp" and so on...)
This script work perfectly. But now there is my question: how can I do the same on using gameobjects?
If I just change the type "Button" to "GameObject" I don't have the possibility to use "onClick.AddListener". I don't want a script for every single gameobject. I just want something like I posted above.
GameObject itself has no such events.
Sounds like what you are asking for is a custom UnityEvent
You can create your own component like e.g.
public class HighlightController : MonoBehaviour
{
public UnityEvent OnHighlight;
// Somewhere Invoke the event e.g.
public void SetHighlighted()
{
OnHighlight.Invoke();
}
}
where you can add listeners now either via code or via the Inspector (just like the onClicked).
So put this on your GameObjects and then e.g. have fields like
[SerializeField] private HighlightController highlight;
//etc...
private void Awake()
{
highlight.OnHighlight.AddListener(HandleHighlight);
//etc...
}
private void HandleHighlight ()
{
...
}
// Make sure to cleanup listeners when not needed anymore
private void OnDestroy()
{
highlight.OnHighlight.RemoveListener(HandleHighlight);
//etc...
}
Or yes as mentioned as simple event could do the same without the serialization:
public class HighlightController : MonoBehaviour
{
public event Action OnHighlight;
public void SetHighlighted ()
{
OnHighlight?.Invoke();
}
}
and do
private void Awake()
{
highlight.OnHighlight += HandleHighlight;
}
private void OnDestroy ()
{
highlight.OnHighlight -= HandleHighlight;
}
I am making a game where the player first has to choose the type of control to use before playing. The three options being: Keyboard, Controller, Touch
The player must click the button corresponding to his choice. Each button runs this script when clicked on:
public class KeyboardButton : MonoBehaviour {
public static int controller;
public void buttonClick () {
controller = 1;
}
}
In reality, each button as its own script, where the value of controller is different depending on the script ran. The idea is that the value of this integer would be sent over to the script responsible of controlling the player so it will make use of the demanded input type. ie: if the keyboard button is selected, it will run the corresponding script, setting the integer value to 1. After the PlayerController script receives this value, it will know to only accept input from the keyboard.
I have consulted a lot of documentation, but a lot of it contains context-specific C# things that I don't understand and are irrelevant to what I want to do.
Also, I would not like an answer around the lines of: "You don't have to make the player choose a control type, here's how you can make your game accept all types of control at once." I already know all this stuff and there is a reason I want the player to make a choice. Furthermore, I would still like to know a way to transfer integers to be able to be more organized, rather than having a single script that does 90% of the things in the game.
There are three way you can pass value to another script.
GetComponent
You can use GetComponent method to get another script.
public class KeyboardButton : MonoBehaviour {
public int controller;
//this is anotherScript instance
public AnotherScript anotherScript;
Start()
{
anotherScript = GameObject.Find("Name of Object").GetComponent<AnotherScript>();
}
public void buttonClick () {
controller = 1;
anotherScript.sendValue(controller); //send your value to another script
}
}
Singleton
Let AnotherScript be a static Singleton,You can get the instance on other side.
public class AnotherScript : MonoBehaviour
{
//need to be static
public static AnotherScript Current;
Start()
{
if(Current == null)
{
Current = new AnotherScript();
}
}
public void sendValue(int val)
{
//todo
}
}
public class KeyboardButton : MonoBehaviour
{
public int controller;
public void buttonClick () {
controller = 1;
AnotherScript.Current.sendValue(controller);//send your value to another script
}
}
SendMessage
If you want to send a value to otherscript,SendMessage is a simple way you can choose.
ps:SendMessage method can just send a parameter.
public class KeyboardButton : MonoBehaviour
{
public void buttonClick ()
{
controller = 1;
GameObject.Find("name of object").SendMessage("sendValue",controller);
}
}
As pointed out in one of the comments, you already exposed that value, you can refer to is via
Debug.Log(KeyboardButton.controller);
without providing an instance. There's multiple other ways of doing it, as this way is only good to a certain level of complexity, after which it starts to get more muddy, but depending on what you need right know it might get you through. It is one of the valid ways, and probably the simplest one.
You may also want to know when the value has changed, for example you could use UntiyEvent and trigger it when value is changed
public class KeyboardButton : MonoBehaviour {
public UnityEvent OnValueChanged;
public static int controller;
public void buttonClick () {
controller = 1;
OnValueChanged.Invoke();
}
}
this is if you like to wire events in the editor. You could also do:
public class KeyboardButton : MonoBehaviour {
public static UnityEvent OnValueChanged;
public static int controller;
public void buttonClick () {
controller = 1;
OnValueChanged.Invoke();
}
}
the downside is that the event won't show up in the editor,but the upside is that you can set up a trigger without having to have a reference to the KeyboardButton instance that just got clicked.
public class ControllerChangeReactor : MonoBehaviour {
void Start()
{
KeyboardButton.OnValueChanged.AddListener(React); // add event listener
}
void React() // will get called when keyboard is clicked
{
Debug.Log(KeyboardButton.controller);
}
}
This approach can become limiting after you've written a dozen or so of those scripts, but a step up involves tailoring a custom system which is probably not worth it on your level (just yet). You can finish a simple game using the above approach, even if its not the most elegant.
You could also parametrize your script (expose 'WHAT DOES IT CHANGE' in editor), to avoid unnecessary multiplication of code