Unity3D_2019: Adding EventTriggers at runtime - c#

In Unity3D Version 2017 you could add multiple EventTriggers at once by doing this:
for (int i = 0; i < 20; i++)
{
EventTrigger.Entry pDown = new EventTrigger.Entry();
pDown.eventID = EventTriggerType.PointerDown;
pDown.callback.AddListener((eventdata) => { InventorySlotCopy(); });
slots[i].GetComponent<EventTrigger>().triggers.Add(pDown);
EventTrigger.Entry pUp = new EventTrigger.Entry();
pUp.eventID = EventTriggerType.PointerUp;
pUp.callback.AddListener((eventdata) => { InventorySlotInsert(); });
slots[i].GetComponent<EventTrigger>().triggers.Add(pUp);
}
where slots is just an array of GameObjects, each with an Image and an EventTrigger attachted to it.
However, using the same code as above in Unity3D Version 2019 results in adding those EventTriggers but not in assigning the functions to the Listener.
How is that been done in Unity2019?

First of all:
You will not see the added listeners in the Inspector!
In the Inspector you only see the permanent listeners, not the ones added on runtime! These are basically UnityEvent<BaseEventData>'s so see Manual: UnityEvents
When a UnityEvent is added to a MonoBehaviour it appears in the Inspector and persistent callbacks can be added.
The only way to add permanent listeners is either via the Inspector or a custom EditorScript! But this is not what you want to do here anyway.
I would slightly modify your script to make it more secure. I simply log some messages for the methods when they get called to show that they get called.
public class Example : MonoBehaviour
{
// Rather make these directly of type EventTrigger
// So you don't have to use GetComponent later and can be sure that these array only receives
// objects that actually have that Component attached!
public EventTrigger[] slots;
private void Start()
{
// Instead of a hardcoded index either iterate using slots.Length
// or simply directly foreach
foreach (var slot in slots)
{
var pDown = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerDown
};
pDown.callback.AddListener(eventData => { InventorySlotCopy(); });
slot.triggers.Add(pDown);
var pUp = new EventTrigger.Entry
{
eventID = EventTriggerType.PointerUp
};
pUp.callback.AddListener(eventData => { InventorySlotInsert(); });
slot.triggers.Add(pUp);
}
}
private void InventorySlotCopy()
{
Debug.Log(nameof(InventorySlotCopy));
}
private void InventorySlotInsert()
{
Debug.Log(nameof(InventorySlotInsert));
}
}
The rest depends on your setup. We don't know what objects your slots are but there are basically two(three) options:
Option 1 - UI
If these slot objects are UI elements in a Canvas such as Image or Text.
This seems to be the case for your specifically.
Make sure that there is an EventSystem present in the scene.
Usually one gets created when creating the first Canvas.
If not create it now via Hierachy View &rightarrow; right click &rightarrow; UI &rightarrow; EventSystem
Make sure you have the option RayCast Target enabled on your slot's UI Components.
Result:
Options 2 - 3D Objects (Option 3 - 3D Objects with 2D Colliders)
Just adding this for other users.
If these objects are not UI elements but rather 3D objects make sure
Again you need the EventSystem as before
The slots have Collider Components attached to the same GameObject the EvenTrigger Component is attached to or any of its children (it may be nested).
Your Camera needs the Component PhysicsRaycaster attached. Make sure here that the eventMask includes the layer(s) your slot objects are placed on. (For Option 3 using 2D Colliders instead use the Physics2DRaycaster)
Result:
As you can see the methods will not be visible in the Inspector (due to the reason mentioned before) but the added listener methods still get called.

Related

Objects continue to be added to the List

I have 2 Targets in the world that I want to add to a list when the Player comes close to them.
I first use the Physics OverlapBox method to return an array of colliders. After this, I run a for loop in which the 2 targets should get added to the list. Only 2 target objects are in the scene but the list gets occupied with hundreds of copies of those objects.
Code Down below
private void TrySelectTarget(bool switchInput)
{
targetArray = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore);
for (int i = 0; i < targetArray.Length ; i++)
{
if (targetArray[i].TryGetComponent<Target>(out Target target))
{
availableTargets.Add(target);
}
}
}
I did a deblug.Log on targetarray.Length and it returned 2, so I don't understand why so many objects are being added to the availableTargets List.
I am calling the TrySelectTarget() method in Update().
I am new to c# and programming, so apologies if I am making a stupid mistake.
Thank you for the help.
With your code everytime Physics.OverlapBox returns hits you add the same objects that have already references stored in the list, again. To simply solve the issue of not having duplicates being stored you should check if an object is already referenced (has an entry in the list). Do that by doing:
if (targetArray[i].TryGetComponent<Target>(out Target target))
{
if (!availableTargets.Contains(target)) availableTargets.Add(target);
}
That will not solve the issue of targets not being removed when not in range anymore though. If that is needed then you should change your code so that the list gets cleared before any new references are being added. You could do:
availableTargets.Clear();
targetArray = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore);
for (int i = 0; i < targetArray.Length ; i++)
...
The better solution to solve for this problem in general is to make use of OnTriggerEnter() and OnTriggerExit() messages provided through a Rigidbody component. Those Methods only get invoked if at least one of the interacting objects has a Rigidbody component. Add a Rigidbody to your player object and a collider with the size of the detection range/zone size and set this collider to be IsTrigger. If you dont want that physics affects the object just check the IsKinematic option on the Rigidbody component. In a script on the player then do:
private void OnTriggerEnter (Collider other)
{
if (other.TryGetComponent<Target>(out Target target))
{
// check if not present already to be 100% sure not to get duplicates
// even though it generally shouldn't be happening, better safe than sorry
if (!availableTargets.Contains(target))
{
availableTargets.Add(target);
}
}
}
private void OnTriggerExit (Collider other)
{
if (other.TryGetComponent<Target>(out Target target))
{
availableTargets.Remove(target);
}
}
I think you either want to
availableTargets.Clear();
first so you start with an empty list every time, as anyway you seem to only be interested in the targets overlapping in that instance.
Or you could use Link and do e.g.
using System.Linq;
...
private void TrySelectTarget(bool switchInput)
{
availableTargets = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore)
.Select(col => col.GetComponent<Target>())
.Where(target => target)
.ToList();
}

How to check how many scenes are in a build in Unity

I'm building a mobile game with many scenes (AKA Levels) in Unity, I also intend on adding more levels through updates later in the games life cycle. To cope with this I am attempting the make an automated script that see's how many scenes there are in the Unity build, and create corresponding UI buttons based on a prefab.
For clarity sake I'll note that when I say "scenes that are in ther Unity Build", I mean scenes that are in the build menu:
And already have an index number.
It's not a lot but here is my current script which I have a levelManager in the scene:
private Scene[] levels;
private void Start()
{
/// This is the line I'm requesting help with
levels = // Some way of getting all the scene's in a project put in an array
foreach (var item in levels)
{
// I will handle the button creation and modification later that will go here
}
}
As said you don't create that variable yourself but rather can simply read out SceneManager.sceneCountInBildSettings and then use SceneManager.GetSceneByBuildIndex in order to iterate and get all these scenes like
// Adjust this in the Inspector
// Set this to the index of level 1
[SerializeField] private int startIndex = 1;
var sceneCount = SceneManager.sceneCountInBildSettings;
levels = new Scene[sceneCount - startIndex];
for(var i = startIndex; i < sceneCount; i++)
{
level[i] = SceneManager.GetSceneByBuildIndex(i);
}
Alternatively you could also do it already before runtime via an editor script using EditorBuildSettings.scenes e.g.
#if UNITY_EDITOR
using UnityEditor;
#endif
...
// Make this field serialized so it gets stored together with the scene
[SerializeField] private Scene[] levels;
#if UNITY_EDITOR
[ContextMenu(nameof(StoreScenes))]
private void StoreScenes()
{
levels = EditorBuildSettings.scenes;
EditorUtility.SetDirty(this);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
#endif
you would run this method before the build by going to the Inspector of your component, open the context menu and hit StoreScenes and it will fill your array. The #if UNITY_EDITOR has to wrap anything using the UnityEditor namespace as it will be stripped of in a build and you would get build exceptions otherwise.

Dynamically render GameObject in Unity C#

I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!

Instantiate GameObjects from editor via InitializeOnLoad

I have a programmatically generated board of a certain size. In order to see it, I would normally have to hit run, which is quite annoying and makes it hard to visualize.
Is it possible to make, and thus be able to visualize, the board via InitializeOnLoad?
I gave it a try:
Setup.cs
[InitializeOnLoad]
public class Setup : MonoBehaviour {
public static Map initialMap;
static Setup(){
initialMap = new Map ();
initialMap.createMap ();
}
}
Map.cs
public class Map {
private Tile[,] tiles= new Tile[5,5];
//I had Resources.Load here, but apparently thats not allowed either...
public GameObject defaultObj= new GameObject("MyCreatedGO");
public Map (){
Debug.Log("In Constructor");
}
public void createMap(){
for (int x = 0; x < tiles.GetLength(0); x += 1) {
for (int y = 0; y < tiles.GetLength(1); y += 1) {
// Tile Instantiates the defaultObj
tiles [x, y] = new Tile (defaultObj, new Vector3 (x, y, 0));
}
}
}
}
But unity responded by complaining that
UnityException: Internal_CreateGameObject is not allowed to be called
from a MonoBehaviour constructor (or instance field initializer), call
it in Awake or Start instead. Called from MonoBehaviour 'Setup' on
game object 'Map'.
Is the engine even in a state where it can make objects when the InitializeOnLoad-ed static constructor happens?
If this isn't possible, how are you supposed to visualize a procedurally generated map in Unity?
I'm not sure if Unity can do that, but you can work around it.
Make a width and height variable, then expose them to the editor - either by making them public or by [SerializeField] attribute, and then edit them in the inspector while the editor is running the game. Use those variables for your x and y values in createMap(). Then you'd just have to add a section like this in Update(), to generate a new map on left click:
if (Input.GetMouseButtonDown(0))
{
// call generate map here
}
Check out this tutorial, too: https://unity3d.com/learn/tutorials/projects/procedural-cave-generation-tutorial/cellular-automata
You can create a custom editor for your Setup script.
You can add a couple of buttons to it like Create and Destroy and, onClick, you can execute every code you want at edit-time as opposed to runtime.
Here's the manual with useful informations on how to execute code in the editor and a video tutorial for writing Editor extensions.

Change color of multiple game objects in Unity 5

I would like to change the color of multiple gameobjects in Unity using a single script. I'm kinda lost in the way how to do it. I'm new to Unity and this is some sort of basic training for me.
Unity version: 5.3.4
Observed Behavior:
Added the same script to the other gameobjects and all change to the same color
Expected Behavior:
Change the color of the gameobjects individually
List of things tried:
Using the -FindGameObject-
Tried to acces the materials using the -GameObject-
Tried both at the same time
Thinking in multiple scripts to achieve the results I want
Here's the code
C#:
using UnityEngine;
using System.Collections;
public class ChangeColor : MonoBehaviour
{
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
private Renderer cube;
private Renderer sphere;
void Start ()
{
//Tried here the -FindGameObjectWithTag-
cube = GetComponent<Renderer>();
sphere = GetComponent<Renderer>();
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.A))
{
//Tried here the -FindGameObjectWithTag-
cube.material.color = Color.red;
}
if(Input.GetKeyDown(KeyCode.S))
{
//Tried here the -FindGameObjectWithTag-
sphere.material.color = Color.green;
}
}
}
Maybe I'm doing something wrong, as I said I'm new to Unity, I kindly accept any help, if it is noobfriendly the better.
Thanks
There are a couple different ways you can go about doing this.
If they are all under the same parent without other objects there too you could loop through the children and change their colors
GameObject Parent = GameObject.Find("ParentObject");
for( int x = 0; x > Parent.transform.childCount; x++);
{
Parent.transform.GetChild(x).GetComponent<Renderer>().material.color = Color.red;
}
If there are less than say 20ish, you could create a list and simply drag and drop each transform into the inspector after changing the list's size to the number of objects you have.
/*Make sure you have Using "System.Collections.Generic" at the top */
//put this outside function so that the inspector can see it
public List<Transform> Objs;
// in your function put this (when changing the color)
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red;
}
Similarly to 2, if you want to do it all in code you can do
//Outside function
public List<Transform> Objs;
//inside function
Objs.Add(GameObject.Find("FirstObject").transform);
Objs.Add(GameObject.Find("SecondObject").transform);
//... keep doing this
//now do the same foreach loop as in 2
You could search by tag (if you have a lot of objects) (i would imagine that this would take a bit longer just because it is going through each component but I have no evidence to back that up)
//Outside function
public GameObject[] Objs;
//inside function
Objs = GameObject.FindGameObjectsWithTag("ATagToChangeColor");
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red();
}
And about this comment:
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
If you make them of type GameObject then you should easily be able to access the renderer simply by doing this:
public GameObject cube;
public void Start(){
cube.GetComponent<Renderer>().material.color = Color.red();
}
And making the variables public allows unity's inspector to see the variables so that you can change them in the unity editor without having to open the script.

Categories