Strange Null Reference Error while adding element to IList - c#

I've got a very confusing problem with a null reference error. It reads as:
NullReferenceException: Object reference not set to an instance of an object
GameHandler.Update () (at Assets/Scripts/GameHandler.cs:33)
Here's the lengthy context:
C#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GameHandler : MonoBehaviour {
public GameObject canvas;
IList<GameObject> selected;
GameObject[] troopObjects;
bool isSelecting = false;
Vector3 mousePosition1;
void Update() {
// If we press the left mouse button, save mouse location and begin selection
if (Input.GetMouseButtonDown(0)) {
isSelecting = true;
mousePosition1 = Input.mousePosition;
if(selected != null) {
foreach (GameObject selectedTroop in selected) {
selectedTroop.transform.GetChild(0).gameObject.SetActive(false);
};
};
selected = null;
};
// If we let go of the left mouse button, end selection
if (Input.GetMouseButtonUp(0)) {
isSelecting = false;
};
if (isSelecting) {
troopObjects = GameObject.FindGameObjectsWithTag("Troop");
foreach (GameObject troop in troopObjects) {
if (IsWithinSelectionBounds(troop)) {
selected.Add(troop);
troop.transform.GetChild(0).gameObject.SetActive(true);
};
};
};
if (selected != null) {
};
}
// Use this for initialization
void Start() {
canvas.SetActive(false);
}
void OnGUI() {
if (isSelecting) {
// Create a rect from both mouse positions
var rect = Utils.GetScreenRect(mousePosition1, Input.mousePosition);
Utils.DrawScreenRect(rect, new Color(0.8f, 0.8f, 0.95f, 0.25f));
Utils.DrawScreenRectBorder(rect, 2, new Color(0.8f, 0.8f, 0.95f));
}
}
public bool IsWithinSelectionBounds(GameObject gameObject) {
if (!isSelecting)
return false;
var camera = Camera.main;
var viewportBounds = Utils.GetViewportBounds(camera, mousePosition1, Input.mousePosition);
return viewportBounds.Contains(camera.WorldToViewportPoint(gameObject.transform.position));
}
}
Now the problem code would appear to be this right here:
if (isSelecting) {
troopObjects = GameObject.FindGameObjectsWithTag("Troop");
foreach (GameObject troop in troopObjects) {
if (IsWithinSelectionBounds(troop)) {
selected.Add(troop);
troop.transform.GetChild(0).gameObject.SetActive(true);
};
};
};
What the error is referencing is this:
selected.Add(troop);
It's saying that "troop" is a null reference. Now when I remove this line of code, the rest works just fine. Which makes no sense because right after that problem code, there is this:
troop.transform.GetChild(0).gameObject.SetActive(true);
Which uses that same "troop" reference. I would love for some assistance on this one because it's got me stumped. If any extra information is needed just let me know.

Are you sure that the troop is null, not selected? Your code above checks selected and if it is not null it nulls it.
selected = null;

You are nullifying the instance and then based on some condition trying to add the elements to the NULL object. That is not right.
Please change this line :
selected = null;
to this :
selected.Clear();
And instantiate the list in the beginning :
void Update() {
selected = new List<GameObject>();

Related

OnPointerEnter is not being called

I'm trying to animate the button while it is being gazed on. I have got the following code in which I Raycast from a sphere to find the button that it hits.
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = screenPosition;
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
foreach (var result in results)
{
Debug.Log(result.gameObject.name);
}
In order to animate the button, I'm adding the following code to the button. Unfortunately, the OnPointerEnter or ``OnPointerExit is never being called.
[RequireComponent(typeof(Button))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public Image progressImage;
public bool isEntered = false;
RectTransform rt;
Button _button;
float timeElapsed;
Image cursor;
float GazeActivationTime = 5;
// Use this for initialization
void Awake()
{
_button = GetComponent<Button>();
rt = GetComponent<RectTransform>();
}
void Update()
{
if (isEntered)
{
timeElapsed += Time.deltaTime;
progressImage.fillAmount = Mathf.Clamp(timeElapsed / GazeActivationTime, 0, 1);
if (timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
_button.onClick.Invoke();
progressImage.fillAmount = 0;
isEntered = false;
}
}
else
{
timeElapsed = 0;
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("entered");
isEntered = true;
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("exit");
isEntered = false;
progressImage.fillAmount = 0;
}
#endregion
}
am I missing something ? or is there any other way of achieving this?
I guess your Raycast supposed to trigger the OnPointerEnter on all results.
You will need to use ExecuteEvents.Execute like e.g.
ExecuteEvents.Execute(result.gameObject, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
And will also at some point have to invoke pointerExitHandler.
I would therefore store a HashSet like
using System.Linq;
private HashSet<GameObject> previousEnters = new HashSet<GameObject>();
...
foreach (var result in results.Select(r => r.gameObject))
{
Debug.Log(result.name);
ExecuteEvents.Execute(result, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
// Store the item so you can later invoke exit on them
if(!previousEnters.Contains(result)) previousEnters.Add(result);
}
// This uses Linq in order to get only those entries from previousEnters that are not in the results
var exits = previousEnters.Except(results);
foreach(var item in exits)
{
if(item) ExecuteEvents.Execute(item, eventDataCurrentPosition, ExecuteEvents.pointerExitHandler);
}
You might actually want to implement your own custom PointerInputModule.
Alternative/Example
As a starter you could also use my answer to Using Raycast instead of Gaze Pointer from a while ago where I created a script based on Steam's VR Laserpointer which allows to interact with 3D objects and UI elements.
Note: Typed on smartphone but I hope the idea gets clear

I'm trying to use ReorderableList in Inspector but it's not working good. How should I work with it?

The editor script.
The problem is that I see in the ReorderableList items from index 0 to 17 and then many more item but they are all Missing. And when trying to delete them they are not deleted.
I want to add new items using the MenuItem and the Selection.
I want to add more propertied to per item like Undo so Undo will remove from the gameobject in the list the BoxCollider and the script Whilefun.FPEKit.FPEInteractablePickupScript
The idea is that when I select an gameobject in the hierarchy if I'm doing right click and select Generate as Pickup Item only then add the BoxCollider and the script Whilefun.FPEKit.FPEInteractablePickupScript and only then add that selected gameobject as item to the ReorderableList and if I make in the ReorderableList on one of them items right click with the mouse and select undo then first remove the Box Collider and the Whilefun.FPEKit.FPEInteractablePickupScript and then remove the gameobject item from the ReorderableList.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
[CustomEditor(typeof(PickupObjects))]
public class PickupObjectsEditor : Editor
{
private static List<GameObject> pickeditems = new List<GameObject>();
private static bool picked = false;
[MenuItem("GameObject/Generate as Pickup Item", false, 30)]
public static void GeneratePickupItems()
{
if (Selection.gameObjects.Length > 0)
{
pickeditems.Clear();
for (int i = 0; i < Selection.gameObjects.Length; i++)
{
if (Selection.gameObjects[i].GetComponent<Whilefun.FPEKit.FPEInteractablePickupScript>() == null)
{
Selection.gameObjects[i].AddComponent<BoxCollider>();
Selection.gameObjects[i].AddComponent<Whilefun.FPEKit.FPEInteractablePickupScript>();
}
Selection.gameObjects[i].layer = 9;
Selection.gameObjects[i].tag = "Pickup Item";
pickeditems.Add(Selection.gameObjects[i]);
}
picked = true;
}
}
PickupObjects _target;
private ReorderableList _myList;
public void OnEnable()
{
_target = (PickupObjects)target;
_myList = new ReorderableList(serializedObject, serializedObject.FindProperty("pickUpObjects"), true, true, true, true);
_myList.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "My Reorderable List", EditorStyles.boldLabel);
};
_myList.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) =>
{
var element = _myList.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.ObjectField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
_myList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
And the mono script
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class PickupObjects : MonoBehaviour
{
public List<GameObject> pickUpObjects = new List<GameObject>();
}
This is a screenshot of the ReorderableList. All messed :
As mentioned Missing actually means the reference was set before but according object was deleted/destroyed.
Since you do pickedItems.Clear() that list should never contain more then the selection actually.
In order to add an additional button you can simply extend your drawElement callback like e.g.
public void OnEnable()
{
//_target = (PickupObjects)target;
_myList = new ReorderableList(serializedObject, serializedObject.FindProperty("pickUpObjects"))
{
draggable = true,
// I wouldn't show the Add button
// since you have your own add functionality
displayAdd = false,
// I wouldn't show the remove button either
// since you want a custom button for removing items
displayRemove = false,
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "My Reorderable List", EditorStyles.boldLabel),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
if (index > _myList.serializedProperty.arraySize -1 ) return;
var element = _myList.serializedProperty.GetArrayElementAtIndex(index);
// In order to prevent errors when directly changing the reference
// I would forbid the direct editing of thisfield
EditorGUI.BeginDisabledGroup(true);
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 20, EditorGUIUtility.singleLineHeight), element, GUIContent.none);
}
EditorGUI.EndDisabledGroup();
if (GUI.Button(new Rect(rect.x + rect.width - 20, rect.y, 20, EditorGUIUtility.singleLineHeight), "X"))
{
var obj = (GameObject) element.objectReferenceValue;
if (obj)
{
var boxCollider = obj.GetComponent<BoxCollider>();
if (boxCollider) DestroyImmediate(boxCollider);
var picker = obj.GetComponent<Whilefun.FPEKit.FPEInteractablePickupScript>();
if(picker) DestroyImmediate(picker);
}
if(_myList.serializedProperty.GetArrayElementAtIndex(index).objectReferenceValue != null)
_myList.serializedProperty.DeleteArrayElementAtIndex(index);
_myList.serializedProperty.DeleteArrayElementAtIndex(index);
serializedObject.ApplyModifiedProperties();
}
}
};
}
Also I just noted that you removed the entire part where you actually ADD objects to the list!
public override void OnInspectorGUI()
{
var list = serializedObject.FindProperty("pickUpObjects");
serializedObject.Update();
if(picked)
{
picked = false;
foreach(var newEntry in pickeditems)
{
// check if already contains this item
bool alreadyPresent = false;
for(var i=0; i < list.arraySize; i++)
{
if((GameObject)list.GetArrayElementAtIndex(i).objectReferenceValue == newEntry)
{
alreadyPresent = true;
break;
}
}
if(alreadyPresent) continue;
// Otherwise add via the serializedProperty
list.arraySize++;
list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = newEntry;
}
pickeditems.Clear();
}
_myList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
Since I don't have your component I did it only with the BoxCollider for the demo ;)
As you can see after adding the objects to the list they all hace the BoxCollider, after removing them they do not have it anymore.

Unity IsPointerOverGameObject Issue

I know it's a common problem but none worked. Maybe the button setup is wrong. I have a Panel with no image, and inside a settings button on the top left that once clicked opens another scene. I tried using those 3 methods but none works. It always detects it as a Game object.
The bool isGameStarted is checking if the player should move or no.
Please guide me through
Have tried
if (Swipe.Instance.Tap && !isGameStarted)
{
if (EventSystem.current.IsPointerOverGameObject() )
{
isGameStarted = false;
}
else
{
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
}
Also tried using trigger but it goes through the UI.
This is the original code.
if (Swipe.Instance.Tap && !isGameStarted)
{
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
Once you tap on the screen the player starts moving. I need it NOT to move or start the game if clicked on the settings button.
I had the same problem until I figured out that IsPointerOverGameObject seems to return true for any GameObject (possibly with a collider) and not only UI objects.
So I wrote a custom static class to check for UI objects only. You will need to set the layer of each panel, image, button, etc. to UI for it to work.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public static class MouseOverUILayerObject
{
public static bool IsPointerOverUIObject()
{
PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
for (int i = 0; i < results.Count; i++)
{
if (results[i].gameObject.layer == 5) //5 = UI layer
{
return true;
}
}
return false;
}
}
Use it like this:
private void OnMouseDown()
{
if (!MouseOverUILayerObject.IsPointerOverUIObject())
HandleClick();
}
The overload of IsPointerOverGameObject takes a parameter
int pointerId Pointer (touch / mouse) ID.
and
If you use IsPointerOverGameObject() without a parameter, it points to the "left mouse button" (pointerId = -1);
therefore when you use IsPointerOverGameObject for touch, you should consider passing a pointerId to it.
So from the example
if (Swipe.Instance.Tap && !isGameStarted)
{
if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))
{
isGameStarted = false;
return;
}
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
otherwise if you have multiple possible touches you could also do the check for all of them like
if (Swipe.Instance.Tap && !isGameStarted)
{
foreach(var touch in Input.touches)
{
if (EventSystem.current.IsPointerOverGameObject(touch.fingerId))
{
isGameStarted = false;
return;
}
}
isGameStarted = true;
motor.StartRunning();
gameCanvas.SetTrigger("Show");
}
also look at this thread

How to use Unity editor script to drag asset object to Scene View like Unity editor > Project view

For instance, I found out that the Unity plugin "Octave3d Level Design" has this feature
I hope to make my own prefab manager, since Octave3d prefab manager is not fit for my need. So the question is: when mouse is overlapping Scene View, how to make mouse seize a object and put it in Scene View?
Edit:
Actually found out that you'll need to use SceneView.lastActiveSceneView.camera as your camera, and you can use Gizmos.DrawMesh() to draw the mesh instead of instantiating..
There's also SceneView.OnSceneGUIDelegate that can help you access the scene view.
here's some sample code on how to set that up
void OnGUI() {
if (objectThatFollowsMouse != null) {
SceneView.onSceneGUIDelegate -= OnSceneGUI;
SceneView.onSceneGUIDelegate += OnSceneGUI;
}
else { SceneView.onSceneGUIDelegate -= OnSceneGUI; }
}
void OnSceneGUI(SceneView sceneView) {
...
}
with some additional editor scripting, you can do something like this
Vector3 mousePoint = Camera.main.ViewportToWorldPoint(Input.mousePosition);
if (objectShouldFollowMouse) {
objectThatFollowsMouse.transform.position = mousePoint;
if (Event.current.type == EventType.MouseUp) {
objectShouldFollowMouse = false;
objectThatFollowsMouse = null;
}
}
if (prefabGotClicked) {
GameObject obj = Object.Instantiate(someObject,mousePoint,Quaternion.identity) as GameObject;
objectShouldFollowMouse = true;
objectThatFollowsMouse = obj;
}
In Octave3d, they implemented this function by:
When select the prefab in the custom editor window, instantiate a preview gameobject of the prefab in Scene, the preview gameobject will follow the mouse (by their custom raycast detection). The preview gameobject's parent is the octave manager or something like this.
When click in the scene, instantiate another gameobject in the scene at the mouse-scene intersection point.
And I think it was a bit overkill, something like Unity's own DragAndDrop is enough: a prefab can be drag from custom editorwindow, and drop on sceneview, hierarchy or object field. And this CUSTOM drag and drop can be implement like this:
In your custom editor window, listen to MouseDownEvent to start the DragAndDrop
In your custom editor window, listen to DragUpdatedEvent to move the dragging object
If you want to detect the Dropping on SceneView or Hierarchy, listen to DragPerform and DragExited in there ongui callback.
Code is here:
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class MyWindow : EditorWindow
{
[MenuItem("Window/MyWindow")]
private static void ShowWindow()
{
GetWindow<MyWindow>("MyWindow");
}
private GameObject prefab;
private bool m_IsDragPerformed = false;
private bool m_IsDragging = false;
private void OnEnable()
{
prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab");
var box = new VisualElement();
box.style.backgroundColor = Color.red;
box.style.flexGrow = 1f;
box.RegisterCallback<MouseDownEvent>(evt =>
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.StartDrag("Dragging");
DragAndDrop.objectReferences = new Object[] { prefab };
Selection.activeGameObject = null;
m_IsDragPerformed = false;
m_IsDragging = true;
});
box.RegisterCallback<DragUpdatedEvent>(evt =>
{
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
});
rootVisualElement.Add(box);
SceneView.duringSceneGui += sv => OnDragEnd();
EditorApplication.hierarchyWindowItemOnGUI += (id, rect) => OnDragEnd();
}
private void OnDragEnd()
{
if (Event.current.type == EventType.DragPerform)
{
m_IsDragPerformed = true;
}
if (Event.current.type == EventType.DragExited)
{
if (m_IsDragging && m_IsDragPerformed)
{
m_IsDragging = false;
m_IsDragPerformed = false;
var go = Selection.activeGameObject;
// Do your **OnDragEnd callback on go** here
}
}
}
private void OnDestroy()
{
SceneView.duringSceneGui -= sv => OnDragEnd();
EditorApplication.hierarchyWindowItemOnGUI -= (id, rect) => OnDragEnd();
}
}
Reference is here, this guy is a genius: How to start DragAndDrop action so that SceneView and Hierarchy accept it like with prefabs

How to show 3D text notification/toast

Is there any simple way to show text notification in front of the camera (while in the game) and make it follow the camera while the player is moving?
We are creating a game where the player can give commands with voice, and when the command is recognized, some text notification must be shown (like toast notifications in Android).
I know about text meshes and that we can put it in front of the camera and show/hide it, but we need it to be done programmatically.
I ended up with creating a class which generates toast on fly.
Code is something like this (initial version, needs small re-factorizations):
using UnityEngine;
public class ToastText3D : MonoBehaviour
{
public static ToastText3D Instance { get; private set; }
private static bool isShowingTextMessage;
private static GameObject textMeshGameObject;
// Preventing instantiation of class by making constructor protoected (or private)
protected ToastText3D() { }
private void Awake()
{
// Check if instance already exists
if (Instance == null)
// if not, set instance to this
Instance = this;
else if (Instance != this)
// Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
Destroy(gameObject);
// Sets this to not be destroyed when reloading scene
DontDestroyOnLoad(gameObject);
}
private void Update()
{
if (!isShowingTextMessage)
return;
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
float distanceFromCamera = 2; // TODO: Move this to const
Vector3 desiredPosition = headPosition + gazeDirection * distanceFromCamera;
textMeshGameObject.transform.position = desiredPosition;
// Rotate the object to face the user.
Quaternion toQuat = Camera.main.transform.localRotation;
toQuat.x = 0;
toQuat.z = 0;
textMeshGameObject.transform.rotation = toQuat;
}
// Returns current text mesh or creates new one if there is no
private TextMesh getCurrentTextMesh()
{
if (!textMeshGameObject)
textMeshGameObject = new GameObject();
TextMesh curTextMesh = textMeshGameObject.GetComponent(typeof(TextMesh)) as TextMesh;
if (!curTextMesh)
{
// TODO: Make constants
curTextMesh = textMeshGameObject.AddComponent(typeof(TextMesh)) as TextMesh;
MeshRenderer meshRenderer = textMeshGameObject.GetComponent(typeof(MeshRenderer)) as MeshRenderer;
meshRenderer.enabled = true;
curTextMesh.anchor = TextAnchor.MiddleCenter;
curTextMesh.alignment = TextAlignment.Center;
curTextMesh.fontStyle = FontStyle.Bold;
curTextMesh.fontSize = 20;
curTextMesh.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
}
return curTextMesh;
}
public void Show3DTextToast(string textToShow, int timeout = 5)
{
if (timeout < 0 || textToShow == "")
return;
TextMesh curTextMesh = getCurrentTextMesh();
curTextMesh.text = textToShow;
textMeshGameObject.SetActive(true);
isShowingTextMessage = true;
// Canceling message hiding invokation if there was any
CancelInvoke("Hide3DTextToast"); // TODO: Move function name to const
// Hiding text if there is any timeout
if (timeout != 0)
Invoke("Hide3DTextToast", timeout);
}
public void Hide3DTextToast()
{
textMeshGameObject.SetActive(false);
isShowingTextMessage = false;
}
}
Now all we need is to call function Show3DTextToast by passing him text and interval.
Example:
ToastText3D.Instance.Show3DTextToast("Text Message", 10);

Categories

Resources