Programmatically Add Objects to a UnityEvent and set the Function - c#

I am trying to add an object to a UnityEvent listener and set that listeners function to Raise() function as the trigger. Like the below screenshot. That way I won't forget to drag and drop the object, or by chance selected the wrong function;
I have some a custom GameEvent that I use through out my game that contains the Raise() function;
public class GameEvent : ScriptableObject
{
private readonly List<GameEventListener> eventListeners = new List<GameEventListener>();
public void Raise()
{
for(int i = eventListeners.Count -1; i >= 0; i--)
eventListeners[i].OnEventRaised();
}
public void RegisterListener(GameEventListener listener)
{
if (!eventListeners.Contains(listener))
eventListeners.Add(listener);
}
public void UnregisterListener(GameEventListener listener)
{
if (eventListeners.Contains(listener))
eventListeners.Remove(listener);
}
}
To make my life easier, I have created an Editor script that will populate two UnityEvents for me.
I can find the two custom GameEvents I want to add. OnCameraWillTransition and OnCameraFinishedTransition by using:
string[] eventAssets = AssetDatabase.FindAssets("OnCamera t:GameEvent", new[] { "Assets/_Scripts/Library/Camera/Events" });
Then I loop through each of the two events and check their names, and add them to whatever class needs them set. In this example, I am using ProCamera2D and it has two events that I want to set. It's referenced in the screenshot above. This is just an example. I would like to be able to pre-poulate any class that has some UnityEvent.
ProCamera2DRooms pr = m_cam.gameObject.AddComponent<ProCamera2DRooms>();
foreach (string s in eventAssets)
{
string path = AssetDatabase.GUIDToAssetPath(s);
if (path.Contains("OnCameraFinishedTransition"))
{
Debug.Log("OnCameraFinishedTransition");
GameEvent e = (GameEvent)AssetDatabase.LoadAssetAtPath(path, typeof(GameEvent));
// This doesn't work, and I have tried different methods
pr.OnStartedTransition.AddListener( (e)=> {e.Raise()} ) );
}
//if (path.Contains("OnCameraWillTransition"))
//Haven't Started
}
Is there any way to pre-populate UnityEvent's programmatically?

I tried to do the same just a few days ago.
I had an example script made just to activate/deactivate a unityevent:
void SetEventTriggerState(EventTrigger ET, EventTriggerType ETType, string MethodName, UnityEventCallState NewState) {
for (int i = 0; i < ET.triggers.Count; i++) {
EventTrigger.Entry Trigger = ET.triggers[i];
EventTrigger.TriggerEvent CB = Trigger.callback;
for (int j = 0; j < CB.GetPersistentEventCount(); j++) {
if (CB.GetPersistentMethodName(j) == MethodName && Trigger.eventID == ETType) {
CB.SetPersistentListenerState(j, NewState);
}
}
}
}
You call it like that (the button that activate the trigger event, event type and event name that you need to change, new state that you need to set)
SetEventTriggerState(actionButtonEventTrigger, EventTriggerType.PointerDown, "JumpButton", UnityEventCallState.EditorAndRuntime);
I tried to add a line like this:
CB.SetPersistentTarget(j, newGameObject); //after the SetPersistentListenerState line
But I discovered that there is no way to do something like that (there is only the GetPersistentTarget method and I found no alternative way to change the target object at runtime).
Not sure if you can add it at runtime but there is an alternative way to do everything at runtime that I found:
First you need to implement the interface you need:
IDragHandler, IPointerUpHandler, IPointerDownHandler
Those are just examples (I'm not sure what you need for the ProCamera2D since I'm not using it), the first one is for the Drag Event, the second one is for the PointerUp event and the last is for the PointerDown event.
After that I just implement the method I want to use with the object I need to use:
public virtual void OnPointerDown(PointerEventData ped) {
currentObject.GetComponent<ScriptOfThatObject>().MethodIWantToUse();
}
to decide what Object will use that method I created a setter method
public GameObject CurrentObject {
set => CurrentObject= value;
}
And I set that GameObject OnTriggerEnter
public void OnTriggerEnter(Collider other) {
publicScriptOfMyButtonReference.CurrentObject = other.gameObject;
}
This is just an example of how to implement Event without using the UnityEvent.EventTrigger component and just use code.
I will not be able to be more specific since I don't use that Camera script but you should be able to implement a solution with those few things in mind.

Related

How to Draw a list of non-MonoBehaviour Objects, that Inherits same Class in Unity Editor?

I have a List<AbilityEffect> effects and a lot of sub-classes of AbilityEffect, such as DamageEffect, HealEffect e.t.c. that HAVE [System.Serializable] property on it.
If I create class with field such as DamageEffect - Default editor will draw it perfectly! (And other effects too!)
I've added a ContextMenu Attribute to this function in AbilityData.cs
[ContextMenu(Add/DamageEffect)]
public static void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect!
I've write some Custom Editor for class, that contains List<AbilityEffect> effects = new List<AbilitiEffect>(), write code that draws a custom list! But how do I tell a Editor to draw a DamageEffect specifically, NOT AbilityEffect?
I'll put some code below:
Ability Data Class
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "New Ability", menuName = "ScriptableObject/Ability")]
public class AbilityData : ScriptableObject
{
public int cooldown = 0;
public int range = 1;
public List<AbilityEffect> effects = new List<AbilityEffect>();
public bool showEffects = false;
[ContextMenu("Add/DamageEffect")]
public void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
}
Ability Data Editor Class
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
public override void OnInspectorGUI()
{
var ability = (AbilityData)target;
DrawDetails(ability);
DrawEffects(ability);
}
private static void DrawEffects(AbilityData ability)
{
EditorGUILayout.Space();
ability.showEffects = EditorGUILayout.Foldout(ability.showEffects, "Effects", true);
if (ability.showEffects)
{
EditorGUI.indentLevel++;
List<AbilityEffect> effects = ability.effects;
int size = Mathf.Max(0, EditorGUILayout.IntField("Size", effects.Count));
while (size > effects.Count)
{
effects.Add(null);
}
while (size < effects.Count)
{
effects.RemoveAt(effects.Count - 1);
}
for (int i = 0; i < effects.Count; i++)
{
DrawEffect(effects[i], i);
}
EditorGUI.indentLevel--;
}
}
private static void DrawDetails(AbilityData ability)
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Cooldown", GUILayout.MaxWidth(60));
ability.cooldown = EditorGUILayout.IntField(ability.cooldown);
EditorGUILayout.LabelField("Range", GUILayout.MaxWidth(40));
ability.range = EditorGUILayout.IntField(ability.range);
EditorGUILayout.EndHorizontal();
}
private static void DrawEffect(AbilityEffect effect, int index)
{
//if (effect is DamageEffect)
// effect = EditorGUILayout
// HOW??
}
}
Ability Effect class (NOT ABSTRACT)
[System.Serializable]
public class AbilityEffect
{
public virtual void Affect() { }
}
Damage Effect Class
[System.Serializable]
public class DamageEffect : AbilityEffect
{
public int damageAmout = 1;
public override void Affect() { ... }
}
Because of how Serialization works, once you deserialize some data Unity will try to populate an object instance based on the type specified in the class definition. If you have a List<AbilityEffect> Unity won't be able to differentiate which specific AbilityEffect you previously serialized. There is really one solution, change AbilityEffect to be a ScriptableObject, so that Unity doesn't actually serialize them as raw data but as GUID references, so that the referenced assets know by themselves what subtype of AbilityEffect they are. The downside is that this way all your effects will have to be assets in your Assets folder.
First of all: Note that since Unity 2021 the foldout is built-in default for all lists and arrays so actually I see absolutely no need for a custom editor at all really (at least for the list part) ;)
There is a couple of problems with your approach.
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect.
Yes, because it is serialized only as a AbilityEffect! For the Serializer all items in that list are of type AbilityEffect and it won't go any further.
So even if you can manage to add subclass items it will only be temporary! After e.g. saving, closing Unity and reopening all the subtypes should be converted to AbilityEffect because that's tue only type the Serializer actually sees for those.
My recommendation would be to rather make your AbilityEffect also of type ScriptableObject. This way you don't even have to bother with a custom drawer for them at all and could have as many instances with different types and configurations as you want, reuse them etc.
This said now a general thing: Don't directly go through the target in editors! (except you know exactly what you are doing)
This doesn't mark this object as "dirty", doesn't work with Undo/Redo and worst of all - it won't save these changes persistently!
Always rather go through the serializedObject and the SerializedPropertys.
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
SerializedProperty cooldown;
SerializedProperty range;
SerializedProperty effects;
SerializedProperty showEffects;
private void OnEnable ()
{
// Link up the serialized fields you will access
cooldown = serializedObject.FindProperty(nameof(AbilityData.cooldown));
range = serializedObject.FindProperty(nameof(AbilityData.range));
effects = serializedObject.FindProperty(nameof(AbilityData.effects));
showEffects = serializedObject.FindProperty(nameof(AbilityData.showEffects));
}
public override void OnInspectorGUI()
{
// refresh current actual values into the editor
serializedObject.Update();
DrawDetails();
DrawEffects();
// write back any changed values from the editor back to the actual object
// This handles all marking dirty, saving and handles Undo/Redo
serializedObject.ApplyModifiedProperties();
}
private void DrawEffects()
{
// Now always ever only read and set values via the SerializedPropertys
EditorGUILayout.Space();
showEffects.boolValue = EditorGUILayout.Foldout(showEffects.boolValue, effects.displayName, true);
if (showEffects.boolValue)
{
EditorGUI.indentLevel++;
// This already handles all the list drawing by default
EditorGUILayout.PropertyField(effects, GUIContent.none, true);
EditorGUI.indentLevel--;
}
}
private void DrawDetails()
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(cooldown.displayName, GUILayout.MaxWidth(60));
cooldown.intValue = EditorGUILayout.IntField(cooldown.intValue);
EditorGUILayout.LabelField(range.displayName, GUILayout.MaxWidth(40));
range.intValue = EditorGUILayout.IntField(range.intValue);
EditorGUILayout.EndHorizontal();
}
}
Now if you really really want to customize the behavior of the list drawing you could use a ReorderableList and can then implement a drawer for each element and there you could indeed perform a type check.
But as said I wouldn't go this way at all since the Serializer doesn't support it anyway.

Unity C#, How script know when public variable(not property) changed

For example of UnityEngine.UI.Text
It's contains text variable for set the value of text as string.
Now, In my own class I use property(get;set) instead directly access the variable, This let me able to update views or fire some events when new value have set. The problem is when using property(get set) it'll not show this field in inspector and even in integration test tools.
But Unity's text component decided not to use get set. Which in my opinion I feel not comfortable for deal with value changed.
Question is how original UnityEngine.UI.Text in text variable can dealing with this?
I think It not just OnValidate() because it can only work on editor. But text variable also work on runtime scripting.
This function is called when the script is loaded or a value is
changed in the inspector (Called in the editor only).
https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html
If you are not using a monobehavior (so you can do OnValidate()) or want to do polling than you could opt for going for an propertyattribute string to method reflection solution along the lines of this gist. Also below a simple implementation and example:
public class OnChangedCallAttribute : PropertyAttribute
{
public string methodName;
public OnChangedCallAttribute(string methodNameNoArguments)=> methodName = methodNameNoArguments;
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(OnChangedCallAttribute))]
public class OnChangedCallAttributePropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property);
if (EditorGUI.EndChangeCheck())
{
OnChangedCallAttribute at = attribute as OnChangedCallAttribute;
MethodInfo method = property.serializedObject.targetObject.GetType().GetMethods().Where(m => m.Name == at.methodName).First();
if (method != null && method.GetParameters().Count() == 0)// Only instantiate methods with 0 parameters
method.Invoke(property.serializedObject.targetObject, null);
}
}
}
#endif
Example:
using UnityEngine;
public class OnChangedCallTester : MonoBehaviour
{
public bool UpdateProp = true;
[SerializeField]
[OnChangedCall("ImChanged")]
private int myPropVar;
public int MyProperty
{
get => myPropVar;
set { myPropVar = value; ImChanged(); }
}
public void ImChanged() => Debug.Log("I have changed to" + myPropVar);
private void Update()
{
if (UpdateProp)
MyProperty++;
}
}
Another solution I used:
public T car;
private T _car;
public Car { get => _car; set => _car = value; }
public void OnValidate() => Car = car;
There is no real way other than to check if the text has changed with respect to a cached "previous" value. In the absence of extension properties you'll simply have to do so manually.
You just can't. That's why most components in Unity poll the value they are relying on every single frame.
If you really want to detect changes, you can do something like this:
public sometype Field;
private sometype _previousValueOfField;
public void Awake()
{
_previousValueOfField = Field;
}
public void Update()
{
if(Field != _previousValueOfField)
{
_previousValueOfField = Field;
// Execute some logic regarding the change of the field there
}
}
(By the way that's another reason why public fields are evil and I hate Unity for relying on them.)

Having Issue with Setting Up multiple Input Field in Unity3d

I'm currently trying to set up multiple Input Field in Unity and having issue do so. I can only get one input to work (intPressure) but don't know how to add a second one (drillpipe). It seem to me that unity only allow one Input Field at a time. I would think that just changing it to InputField2, ect will do the trick, but that doesn't seem to work. Other have mentioned creating an empty gameobject and inserting the inputfield to each gameobject.
To be honest, I still quite confuse about how to setup the second input field.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UserInput : MonoBehaviour {
InputField input;
int intPressure = 0;
int drillpipe = 0;
public DataManager data;
void Start ()
{
var input = gameObject.GetComponent<InputField>();
var se= new InputField.SubmitEvent();
se.AddListener(SubmitName);
input.onEndEdit = se;
}
private void SubmitName(string pressure)
{
var pressureScript =
GameObject.FindObjectOfType(typeof(DataManager)) as DataManager;
if (int.TryParse(pressure, out intPressure))
{
pressureScript.pressure = intPressure;
}
else
{
// Parse fail, show an error or something
}
}
}
I am not sure about the way you want to implement this (using single GameObject), however you most certainly would be able to do this if you had a couple of GameObjects (One object per control) nested within another GameObject (let's call it UserInputRoot) like this:
UserInputRoot (has UserInputController script)
|
+--- InputPressure (has InputField component)
|
+--- InputDrillpipe (has InputField component)
The controlling script would then have either a couple of public InputField's or a couple of private ones, initialized within the Start() or Awake() method:
class UserInputController : MonoBehaviour {
//These can be set from the inspector and controlled from within the script
public InputField PressureInput;
public InputField DrillpipeInput;
// It would be better to pass this reference through
// the inspector instead of searching for it every time
// an input changes
public DataManager dataManager;
private void Start() {
// There is no actual need in creating those SubmitEvent objects.
// You can attach event handlers to existing events without a problem.
PressureInput.onEndEdit.AddListener(SubmitPressureInput);
DrillpipeInput.onEndEdit.AddListener(SubmitDrillpipeInput);
}
private void SubmitDrillpipeInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.drillpipe = result;
}
}
private void SubmitPressureInput(string input) {
int result;
if (int.TryParse(input, out result)) {
dataManager.pressure = result;
}
}
}
And by the way, the formatting of your code is absolutely atrocious. You MUST fix it.
It looks like the script you are showing is attached to an input field gameobject is that correct? You have a member variable in your object named input but then you create a new variable input in your Start method. Rather than have a script attached to your first InputField, create an empty gameobject in the editor and attach a script to that. In the script add two public members:
public class UserInput : MonoBehaviour {
public InputField PressureInput;
public InputField DrillPipeInput;
Now pop back to the editor, you should see the two input fields showing up when you select your empty gameobject. Drag and drop your two input fields into the slots for each inputfield. Now when the scene starts your UserInput script will be set with the input fields and you can use them both.

Accessing elements in a list property from a seperate class.

I have one class that is going to contain multiple lists that hold a variety of game objects. These lists will get populated by JSON scripts further in development.
Right now, I'm trying to access 1 element in 1 list. I have filled my class out as so:
public class ShopManager : MonoBehaviour
{
public List<GameObject> primaryWeapons = new List<GameObject>();
public List<GameObject> PrimaryWeapons
{
get { return primaryWeapons; }
}
public GameObject gameObj1, gameObj2;
// Use this for initialization
void Start ()
{
FillList();
}
public void FillList()
{
primaryWeapons.Add(gameObj1);
primaryWeapons.Add(gameObj2);
}
}
In my second class I'm trying to access one of the game objects I have placed in the list. This what I have so far:
ShopManager primary;
public GameObject temp;
public List<GameObject> tmpList;
void Awake()
{
primary = new ShopManager();
primary.FillList();
tmpList= new List<GameObject>();
}
// Use this for initialization
void Start()
{
for (int i = 0; i < primary.PrimaryWeapons.Count; i++)
{
tmpList.Add(primary.PrimaryWeapons[i]);
}
Debug.Log(primary.PrimaryWeapons.Count);
}
public void SelectWeapon1()
{
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
}
}
In my list in the shop manager class I am manually setting the objects myself, so I know the names of them. However, whilst I can get a count returning correctly, I am unable to access this named object.
When I run the code I get a null reference pointing to the following line:
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
Additonally I even created another list with the idea of passing the contents from my List property in the ShopManager class to this temp one. However, this lists populates with 2 empty positions.
I'm still not 100% on using properties like this. Espcially with lists. Could someone please tell me what it is I'm doing wrong?
You didn't initialize your objects in ShopManager.
Replace this:
public GameObject gameObj1, gameObj2;
With this:
public GameObject gameObj1 = new GameObject(), gameObj2 = new GameObject();
Check GameObject initialization. Especially gameObject field.
primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault(); can fail with null reference in just few conditions:
primary is null
primary.PrimaryWeapons is null
obj.gameObject is null
The first two look covered in your code. So, check how GameObject.gameObject field is initialized.

Store methods in arrays

I have an if statement in a foreach and was wondering if I can call a different method depending on what items are true without having to do a switch or if statements for all of them.
foreach (var item in buttons)
{
if(item.isClicked)
//call item method
}
buttons is not of class Buttons
What I'm looking for is like
button[0] calls method start()
button[1] calls method options()
Is there any way I can do this?
You could do something like this:
private void DoStuff(params Action[] methods) {
for (int i = 0; i < buttons.Length; i++) {
if (buttons[i].isClicked) {
methods[i]();
break;
}
}
}
Then, you would say:
DoStuff(start, options);
Where start is the method called for the first button, options for the second, x for n.. etc.
Assuming your buttons don't support proper events, I suppose what you're looking for is a delegate. There are several ways to do this, but the most obvious thing that comes to mind is something like this:
Action[] actions = new Action[2]; // create an array of actions, with 1 action for each button
actions[0] = start;
actions[1] = options;
...
for(var i = 0; i < buttons.Length; i++)
{
if(buttons[i].isClicked)
actions[i]();
}
There are a couple of ways to achieve this, which way you use really depends on how much access you have to modify the class of your "buttons" (which you haven't actually told us what it is).
Option 1: Add a new member to the class (provided the class of 'buttons' is your own class)
Modify your class to have a new member, called Action or some such name. This member will be a Delegate (note: You can use a more specific type like Action<T> if you know each button's action has an identical method signature. Once you have this member declared, you can easily call it. Pseudocode:
public class MyButton {
public bool isClicked { get; }
public Delegate action { get; }
}
foreach (var item in buttons) {
if(item.isClicked)
((Action)item.action)(); // assuming that your "action" is a method which returns nothing and takes no arguments, cast to a more appropriate type if needed
}
Option 2: Map each button to an action
Similar principal to Option 1, except because you can't directly modify the backing class you'll have to bind actions to buttons after the fact. You can create a map (or Dictionary<TKey,TValue> in C#) to map the buttons to their actions. To do this, create a new Dictionary and add each button as a key alongside its action:
// Declared at class-scope
private readonly Dictionary<MyButton,Delegate> _actions = new Dictionary<MyButton,Delegate>(); // I don't know what type 'buttons' is so I'm substituting it with "MyButton"
// In some initializer for the class (i.e the constructor)
_actions.Add(buttons[0], start)
_actions.Add(buttons[1], options)
// .. etc
// Then your loop becomes something like:
foreach(var item in buttons) {
if (item.isClicked && _actions.ContainsKey(item)) {
((Action)_actions[item])();
}
}

Categories