Can i create custom region with grouped methods for list onClick like dynamic and statics?
like this
Yes and no! ^^
Yes, you can create your own event type taking a parameter and assign dynamic callbacks to it. What you are looking for is UnityEvent.
For the dynamic parameterized ones see UnityEvent<T0> to UnityEvent<T0, T1, T2, T3> depending on how many parameters you need.
For the example with a single int it would be (exactly as in the API example)
// Since Unity doesn't support direct serialization of generics you have to implement this [Serializable] wrapper
[Serializable]
public class MyIntEvent : UnityEvent<int>
{
}
public class ExampleClass : MonoBehaviour
{
public MyIntEvent m_MyEvent;
}
No, you can not simply change the existing implementation of UI.Button.onClick which is parameterless.
What you could do, however, is build a new component and attach it on a button like
[RequireComponent(typeof(Button))]
public class ExampleClass : MonoBehaviour
{
[SerializeField] private Button _button;
public MyIntEvent onClickWithIntParameter;
private void Awake()
{
if(!_button) _button = GetComponent<Button>();
_button.onClick.AddListener(HandleOnClick);
}
private void HandleOnClick()
{
// Wherever you get your int from
var value = 123;
onClickWithIntParameter.Invoke(value);
}
}
In the case that [Serializable] isn't working for you try [System.Serializable] or using System; at the top.
For Those who want to create a custom event Script:
I made a collision detection script. The events set in the inspector are called when a collision is detected. Just like a button.
https://i.stack.imgur.com/r4epP.png
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;
public class CollisionCallFunc : MonoBehaviour {
[System.Serializable]
public class CollisionEvent : UnityEvent<object> {
public object value;
}
[SerializeField]
private CollisionEvent collisionEvents = new();
private void OnCollisionEnter(Collision collision) {
try {
collisionEvents.Invoke(collisionEvents.value);
} catch(System.Exception exception) {
Debug.LogWarning("Couldn't invoke action. Error:");
Debug.LogWarning(exception.Message);
}
}
private void OnCollisionEnter2D(Collision2D collision) {
try {
collisionEvents.Invoke(collisionEvents.value);
} catch(System.Exception exception) {
Debug.LogWarning("Couldn't invoke action. Error:");
Debug.LogWarning(exception.Message);
}
}
}
Related
So I have a 2D platformer parkour game which holds a timer when player starts level. I have 5 levels and at the end of each level, I want to keep the last time value and display them at the end of the game.
So far, I tried to hold the last time value when player triggers the End portal and store them in an array in the code below. Here are the scripts:
Time Data Manager Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
void Start(){
}
void Update(){
Debug.Log(timeDataArr[SceneManager.GetActiveScene().buildIndex-1]);
}
public void AddTimeData(string timeData, int levelBuildIndex){
timeDataArr[levelBuildIndex-1] = timeData.ToString();
}
}
End Portal Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class EndPortal : MonoBehaviour
{
public AudioSource aSrc;
private int sceneNumber;
public GameObject bgAudio;
public Text scoreText;
string textData;
public timeDataManager tDManager;
void Start(){
sceneNumber = SceneManager.GetActiveScene().buildIndex;
tDManager = GameObject.FindGameObjectWithTag("TimeDataManager").GetComponent<timeDataManager>();
}
void OnTriggerEnter2D(Collider2D col){
if (col.gameObject.tag == "Player"){
aSrc.Play();
Destroy(bgAudio);
textData = scoreText.text;
Debug.Log(textData);
Debug.Log(sceneNumber);
tDManager.AddTimeData(textData,sceneNumber);
SceneManager.LoadScene(sceneBuildIndex:sceneNumber+1);
}
}
}
As I said before, I tried to keep all timer values at the end of each level and store them in an array in my timeDataManager script. But it's not working.
Any ideas on how to fix? Or do you have any other ideas? Thanks a lot for your time.
Issylin's answer is a better solution for your needs but i want to mention couple of things about your code.
One thing about your timeDataArr. If you don't touch it in inspector, you need to initialize it first. So let's say, if you want to hold 5 levels of time data, you need to do something like;
public string[] timeDataArr = new string[5];
or
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
public int sceneAmount_TO_HoldTimeData = 5;
void Start()
{
timeDataArr = new string[sceneAmount_TO_HoldTimeData];
}
}
or
public class timeDataManager : MonoBehaviour
{
public string[] timeDataArr;
public int sceneAmount_NOT_ToHoldTimeData = 1;
void Start()
{
timeDataArr = new string[SceneManager.sceneCountInBuildSettings - sceneAmount_NOT_ToHoldTimeData];
}
}
Another thing is you need to be careful about levelBuildIndex-1. If you call that in first scene,
SceneManager.GetActiveScene().buildIndex
will be 0 and levelBuildIndex-1 will be -1 or in Debug part it will be timeDataArr[-1] but array indexes must start with 0. So it will throw an error.
One more thing, this is not an error or problem. Instead of this part
tDManager = GameObject.FindGameObjectWithTag("TimeDataManager").GetComponent<timeDataManager>();
you can do
tDManager = FindObjectOfType<timeDataManager>();
There are several way to achieve what you're trying to do.
The easier would be to use a static class for such a simple data like the one you're carrying through your game.
using System.Collections.Generic;
public class Time_Data
{
/// TODO
/// Add the members
/// e.g. your time format as string or whatever,
/// the scene index, etc
}
public static class Time_Manager
{
private static List<Time_Data> _times;
public static void Add_Time(in Time_Data new_time)
{
_times.Add(new_time);
}
public static List<Time_Data> Get_Times()
{
return _times;
}
public static void Clear_Data()
{
_times.Clear();
}
}
Use it like any other static methods within your OnTriggerEnter2D call.
Time_Manager.Add( /* your new data here */ );
The other way, if you intend to stay with a game object within your scenes, would be to use the DontDestroyOnLoad method from Unity so your game object which has your script timeDataManager would remain.
Note that in your code, using string[] might be unsafe. For your use of it, consider using a List<string>.
I'm trying to synchronize text in a unit between players, but I'm getting an error.
I just need to sync the text
[1]: https://i.stack.imgur.com/0l98U.png
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using UnityEngine.UIElements;
public class Sync : MonoBehaviourPunCallbacks
{
private PhotonView view;
public Text textGame;
public Text copied;
void Start()
{
view = GetComponent<PhotonView>();
}
public void sync()
{
if(view.IsMine)
view.RPC("ViewAll", RpcTarget.AllBuffered, textGame.text);
}
[PunRPC]
public void ViewAll(string tG)
{
tG = copied.text;
Debug.Log(tG);
}
}```
пппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппппп
The error is telling you that the "view" variable has not been assigned and is therefore null.
To assign it you can make that field public, and in the inspector you
assign the photonView you want, I guess that of the player.
Alternatively you can assign "view" not from the inspector but in a
Start() or Awake() method. If the photonView you want to associate is
on the same GameObject as the script, you can write like this:
void Awake()
{
view = GetComponent();
}
However, the easiest answer is to replace "view" with "photonView".
It will have the same result as the solutions described above, but it will be very simple.
enter code here
public void sync()
{
if(photonView.IsMine)
view.RPC("ViewAll", RpcTarget.AllBuffered, textGame.text);
}
I have not directly given you the simple slogan to make you fully understand the problem and to help you solve it.
In my game I have coins, in my coin script I have an OnDestroy() function but I get this error "NullReferenceException: Object reference not set to an instance of an object
coinscript.OnDestroy () (at Assets/Scrips/coinscript.cs:9)"
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class coinscript : MonoBehaviour
{ public gamemanager GameManager;
void OnDestroy()
{
GameManager.plusScore(10);// FindObjectOfType<gamemanager>().pluseScore(10); gets the same error
}
}
fixed it thanks to help from KiynL
using System.Collections.Generic;
using UnityEngine;
public class coinscript : MonoBehaviour
{ public gamemanager GameManager;
bool destroyed = false;
void OnDestroy()
{
if (destroyed = false)
{
GameManager.plusScore(10);
destroyed = true;
}
}
}
This is because the object may be destroyed multiple times in one frame. Fix it.
void OnDestroy()
{
if (gameObject) GameManager.plusScore(10);
}
You need to go into the properties of whatever object you attached that script to, and assign Game Manager an object that has a gamemanager script attached to it.
As ted said its probably because you haven't set GameManager from inspector. you need to drag a gameObject that has gamemanager component on it.
but I recommand making plusScore function static and calling it without an object, if you make it statis you also have to make varible that stores score static as well, some thing like this:
public class gamemanager : MonoBehaviour
{
static int Score = 0;
public static void plusScore(int score)
{
Score += score;
}
}
then calling it like this:
gamemanager.plusScore(10);
I have a class in Unity 2017 that only shows some of the public methods in the inspector.
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
List<ShipPart> _inventory;
int currentInvPosition = 0;
bool invExists = false;
// Use this for initialization
void Start () {
CreateInventory(0, 0);
}
// Show all inventory parts as gameobjects
public void CreateInventory(int quality, int part)
{
...
}
void DestroyInventory()
{
...
}
public void ScrollInvLeft()
{
...
}
public void ScrollInvRight()
{
...
}
void UpdateInv(float offset)
{
...
}
public void AddInventoryItem(ShipPart newShipPart)
{
...
}
public void RemoveInventoryItem(ShipPart oldShipPart)
{
...
}
public void Test1(){}
public void Test2(int i){}
}
I thought it might be because the invisible methods have parameters, so I added the last two methods. However they are visible in the inspector!
I am trying to call the methods from a dropdown UI element, but have also tested from a button and that cant see them either.
What am I doing wrong?
As indicated in the official Unity tutorials, if you want to provide a function to an event in the inspector, the function must meet the following requirements:
The function must be public
The function must have a return type of void
The function must take no or one parameter
If the function takes one parameter, the latter must be one of the following types:
int
float
string
bool
UnityEngine.Object, or any type inheriting from UnityEngine.Object (such as GameObject, MonoBehaviour, ScriptableObject, ...)
What in the name of god am I doing wrong here? Every time I run the game I get this: "NullReferenceException: Object reference not set to an instance of an object". I know what that means I just don't get why it is saying that?
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class Inventory : MonoBehaviour {
public static Inventory instance;
public List<InventoryItems> INVENTORY_ITEMS = new List<InventoryItems>();
void Awake(){
instance = this;
}
void Start(){
Debug.Log(instance.INVENTORY_ITEMS); // ERROR
Debug.Log(INVENTORY_ITEMS); // ERROR
}
}
[Serializable]
public struct InventoryItems
{
public string name;
}
This is how you should write this code:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
public class Inventory : MonoBehaviour {
public static Inventory instance;
public List<InventoryItems> INVENTORY_ITEMS;//do not initialize here
void Awake(){
instance = this;
INVENTORY_ITEMS = new List<InventoryItems>();//init here instead
}
void Start(){
Debug.Log(instance.INVENTORY_ITEMS); //no ERROR
Debug.Log(INVENTORY_ITEMS); //no ERROR
}
}
[Serializable]
public struct InventoryItems
{
public string name;
}
Added note:
The reason for this error is that the value of a public serializable member in monobehaviour is being read from the editor (inspector) overwriting any value being assigned to it prior to Awake.
I suggest either change the list accessor to internal, or make it a property, or keep it public but let inspector handle the initialization and adding the initial elements.
Added another note:
According to Programmer and Uri Popov, your original code most certainly will work on unity 5.4 and later.