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, ...)
Related
When entering the server, the player calls the RPC function that increases the number of players, it works correctly. Another RPC function is responsible for the player's death, but it changes the variable only for one client, the value does not change for the rest.
the script in which the function does not work
using Photon.Pun;
using UnityEngine;
public class PigLifeScript : MonoBehaviour
{
PhotonView view;
private void Start()
{
view = GetComponent<PhotonView>();
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "WolfPlayer")
{
view.RPC("RemovePlayer", RpcTarget.AllViaServer);
}
}
}
Functions
private int AlivePigs;
public Text AlivePigsTXT;
[PunRPC]
public void AddPlayer()
{
AlivePigs++;
}
[PunRPC]
public void RemovePlayer()
{
AlivePigs--;
}
I tried to call RPC from a separate function, tried to make conditions in the Update method, tried to pass the value to the master client,
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>.
when I load a new scene I get this error: NullReferenceException: Object reference not set to an instance of an object GameController+d__16.MoveNext () (at Assets/Scrips/GameController.cs:76) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at :0)
this is my script to save:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Scene_Manager : MonoBehaviour
{
int Saved_scene;
int Scene_index;
public void Load_Saved_Scene()
{
Saved_scene = PlayerPrefs.GetInt("Saved");
if (Saved_scene != 2)
SceneManager.LoadSceneAsync(Saved_scene);
else
return;
}
public void Save_and_Exit()
{
Scene_index = SceneManager.GetActiveScene().buildIndex;
PlayerPrefs.SetInt("Saved", Scene_index);
PlayerPrefs.Save();
SceneManager.LoadSceneAsync(0);
}
public void Next_Scene()
{
Scene_index = SceneManager.GetActiveScene().buildIndex + 1;
SceneManager.LoadSceneAsync(Scene_index);
}
}
and this is my script for the whole game where I call save:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
[RequireComponent(typeof(GameUI))]
public class GameController : MonoBehaviour
{
public static GameController Instance { get; private set; }
[SerializeField]
private int knifeCount;
[Header("Knife Spawning")]
[SerializeField]
private Vector2 knifeSpawnPosition;
[SerializeField]
private GameObject knifeObject;
public GameUI GameUI { get; private set; }
private void Awake()
{
Instance = this;
GameUI = GetComponent<GameUI>();
}
private void Start()
{
GameUI.SetInitialDisplayedKnifeCount(knifeCount);
SpawnKnife();
}
public void OnSuccessfulKnifeHit()
{
if (knifeCount > 0)
{
SpawnKnife();
}
else
{
StartGameOverSequence(true);
}
}
private void SpawnKnife()
{
knifeCount--;
Instantiate(knifeObject, knifeSpawnPosition, Quaternion.identity);
}
public void StartGameOverSequence(bool win)
{
StartCoroutine("GameOverSequenceCoroutine", win);
}
private IEnumerator GameOverSequenceCoroutine(bool win)
{
if (win)
{
yield return new WaitForSecondsRealtime(0.3f);
FindObjectOfType<LevelLoader>().LoadNextLevel();
FindObjectOfType<Scene_Manager>().Save_and_Exit();
}
else
{
GameUI.ShowRestartButton();
}
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex, LoadSceneMode.Single);
}
}
Now first of all take a look at this post to understand NullReferenceExceptions.
After that take a closer look at your error message:
NullReferenceException: Object reference not set to an instance of an object GameController+d__16.MoveNext () (at Assets/Scrips/GameController.cs:76)
The (at Assets/Scrips/GameController.cs:76) part tells you exactly where you error is thrown (which basically means where it occured). It's in your Assets/Scrips/GameController.cs script at line 76.
private IEnumerator GameOverSequenceCoroutine(bool win)
{
if (win)
{
yield return new WaitForSecondsRealtime(0.3f);
FindObjectOfType<LevelLoader>().LoadNextLevel();
FindObjectOfType<Scene_Manager>().Save_and_Exit(); // <--- HERE
}
...
}
In your specific implementation my best guess would be that you shouldn't load your new scene before you try to save & quit. You're starting to load your new scene asynchronously and then call another function which loads another scene (?) while not actually preventing the first scene from activating until your other functions actually executed properly. There's a plethora of cases where this can and will break.
I think you'll need to rethink what it is your actually trying to accomplish because these function calls don't make sense like that.
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);
}
}
}
I am controlling a text GameObject. I am performing operations such as SetActive() and changing the text of the gameObject. Now I am duplicating the same object and I want the duplicated object to follow the behaviors of its main gameObject. That is ObjA is parent and ObjB is a clone. If I change the text UI through code of objA, I want objB to automatically change its component. How do I achieve this behavior?
There are multiple ways to do that, but it will always be by code, there is no "authomatism" to do that.
So by code you can make a relation child to parent or parent to child.
One way could be that one:
public class Parent : MonoBehaviour
{
public Child child = null;
public void DoSomething()
{
this.gameObject.SetActive(true);
child.DoSomething();
}
}
public class Child : MonoBehaviour
{
public void DoSomething()
{
this.gameObject.SetActive(true);
}
}
Another fancy way to do that is using delegates, or Actions:
public class Parent : MonoBehaviour
{
public Action OnDoSomething = null;
public Action OnDoSomethingElse = null;
public void DoSomething()
{
this.gameObject.SetActive(false);
OnDoSomething();
}
public void DoSomethingElse()
{
this.gameObject.SetActive(true);
OnDoSomethingElse();
}
}
public class Child : MonoBehaviour
{
public Parent parent = null;
public void Awake()
{
parent.OnDoSomething += ChildDoSometing;
parent.OnDoSomethingElse += ChildDoSometingElse;
}
public void OnDestroy()
{
parent.OnDoSomething -= ChildDoSometing;
parent.OnDoSomethingElse -= ChildDoSometingElse;
}
public void ChildDoSometing()
{
this.gameObject.SetActive(false);
}
public void ChildDoSometingElse()
{
this.gameObject.SetActive(false);
}
}
You can be tempted to pass your own method as Action parameter like Action<Action> but remember that child will call parent method, won't operate on his own. So in this case if you do something like:
public class Parent : MonoBehaviour
{
public Action<Action> OnDoSomething = null;
[ContextMenu("A")]
public void DoSomething()
{
this.gameObject.SetActive(true);
OnDoSomething(this.DoSomething);
}
public void DoSomethingElse()
{
print("Hello");
}
}
public class Child : MonoBehaviour
{
public Parent parent = null;
public void Awake()
{
parent.OnDoSomething += RepeatedAction;
}
public void OnDestroy()
{
parent.OnDoSomething -= RepeatedAction;
}
public void RepeatedAction(Action actionToRepeat)
{
actionToRepeat?.Invoke();
}
}
Will result on StackOverflow exception, cause child will call parent, who calls child, who calls again parent...you can see the problem.
Anyway would be nice to declare an abstract base class that have all the methods, and let both classes inherit from that class, and implement those methods.