Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Implementing drag and drop behavior from GUI button to 3D world space.

Discussion in 'Scripting' started by LordBlackwood, Jun 22, 2015.

  1. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    Hey, I'm having a little trouble pinpointing what I'm doing wrong here and would greatly appreciate a nudge in the right direction. I have a panel of buttons that are going to have pictures of units on them. I want the player to eventually be able to click on these buttons to instantiate a "ghost unit", then without lifting their finger, drag and drop that unit on the terrain, after which the ghost will be destroyed and be replaced by the proper unit. Right now the behavior is unpredictable. Sometimes it works ok, and other times I get a MissingReferenceException or a NullReferenceException.

    I'm certain I'm performing poor practices, could somebody help me see what I'm doing wrong?

    Much appreciated!

    Code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.EventSystems;
    4.  
    5. public class DragManagerScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler {
    6.  
    7.     public GameObject vehicle;
    8.     public GameObject ghost;
    9.     private GameObject currentVehicle;
    10.  
    11.     RaycastHit hit;
    12.     private Vector3 specificVector;
    13.     public LayerMask acceptableLayer;
    14.  
    15.     public void OnBeginDrag(PointerEventData eventData)
    16.     {
    17.         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    18.  
    19.         if (Physics.Raycast(ray, out hit, acceptableLayer))
    20.         {
    21.             if (hit.collider.name == "ground")
    22.             {
    23.                 specificVector.Set(hit.point.x, hit.collider.transform.position.y, hit.point.z);
    24.                 currentVehicle = Instantiate(ghost, specificVector, Quaternion.identity) as GameObject;
    25.             }
    26.  
    27.         }
    28.     }
    29.  
    30.     public void OnDrag(PointerEventData eventData)
    31.     {
    32.         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    33.  
    34.         if (Physics.Raycast(ray, out hit, acceptableLayer))
    35.         {
    36.             if (hit.collider.name == "ground")
    37.             {
    38.                 specificVector.Set(hit.point.x, hit.collider.transform.position.y, hit.point.z);
    39.                 currentVehicle.transform.position = specificVector;
    40.                 Time.timeScale = 0;
    41.             }
    42.         }
    43.     }
    44.  
    45.     public void OnEndDrag(PointerEventData eventData)
    46.     {
    47.         Destroy(currentVehicle);
    48.  
    49.         Instantiate(vehicle, specificVector, Quaternion.identity);
    50.         Time.timeScale = 1;
    51.     }
    52. }
    53.  
    At the moment the code is attached to my button.
     
  2. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    it's usually helpful to include the full text of the errors you're getting so we can see which line numbers are causing issues specifically.
     
  3. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    Ah, makes sense. Thanks, here they are:

    Error NullReferenceException: Object reference not set to an instance of an object Solution 'UnityVS.test' ‎(4 projects) Assets/Scripts/DragManagerScript.cs 40

    Error MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.

    Your script should either check if it is null or you should not destroy the object. Solution 'UnityVS.test' ‎(4 projects) Assets/Scripts/DragManagerScript.cs 40
     
  4. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    At some point it seems to stop instantiating the ghost and each new "real unit" drops in at the same position as the last one. So what will happen is I'll drag and drop two or three more or less successfully, then I'll start dragging and the ghost wont appear and all new units just start piling on top of eachother on the last value of "specificVector."
     
  5. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    hmmm, I think those errors would suggest the OnDrag and OnEndDrag are getting called in the same frame in some instances... what happens if you add in

    Code (csharp):
    1.  
    2. public void OnDrag(PointerEventData eventData)
    3. {
    4. if(currentVehicle)
    5. {
    6. // all the code from OnDrag
    7. }
    8. }
    9.  
     
  6. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    It works a couple of times and then suddenly stops making the ghost and starts stacking them again. This time no errors though. Very rarely sometimes when I do several in quick succession it will drop it somewhere else.
     
  7. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    stacking would suggest that line 23 in the op snippet isn't being reached (i.e. specificpoint isn't being updated to the new point) and the lack of ghost is line 24... probably worth putting some debug.log lines in around the logic in the OnDrag function and see if there is something interfering with the Raycast (something in the wrong layer etc.?).
     
  8. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    Ugh, you're right. I've used log messages and sometimes the Raycast just doesn't work. Not sure how this could be, the game consists of nothing more than one massive ground with a simple box collider. Other than the gui button there is literally nothing between my camera and the ground, yet 50/50 it doesnt work (the more drag and drops I do, the worse the performance seems to be).

    I didn't like how the drag was behaving (Only fires when the mouse is moving over a certain threshold?) so I tried a different method using a singleton Manager script that recieves a drag event from the button. However, it happens just the same. Probbably because for whatever reason Raycast is failing...

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.EventSystems;
    4.  
    5. public class DragAndDropManager : MonoBehaviour
    6. {
    7.     public static DragAndDropManager instance;
    8.  
    9.     public GameObject ghost;
    10.     public GameObject vehicle;
    11.  
    12.     private bool buttonClicked;
    13.     private GameObject currentGhost;
    14.  
    15.     RaycastHit hit;
    16.     private Vector3 specificVector;
    17.     public LayerMask acceptableLayer;
    18.  
    19.     void Awake()
    20.     {
    21.         instance = this;
    22.     }
    23.  
    24.     public void SpawnObject()
    25.     {
    26.         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    27.  
    28.         if (Physics.Raycast(ray, out hit, acceptableLayer))
    29.         {
    30.             if (hit.collider.name == "ground")
    31.             {
    32.                 Debug.Log("Raycast success.");
    33.                 specificVector.Set(hit.point.x, hit.collider.transform.position.y, hit.point.z);
    34.                 currentGhost = Instantiate(ghost, specificVector, Quaternion.identity) as GameObject;
    35.  
    36.                 buttonClicked = true;
    37.             }
    38.  
    39.         }
    40.     }
    41.  
    42.     public void Update()
    43.     {
    44.         if (Input.GetMouseButton(0) && buttonClicked)
    45.         {
    46.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    47.  
    48.             if (Physics.Raycast(ray, out hit, acceptableLayer))
    49.             {
    50.                 if (hit.collider.name == "ground")
    51.                 {
    52.  
    53.                     specificVector.Set(hit.point.x, hit.collider.transform.position.y, hit.point.z);
    54.                     currentGhost.transform.position = specificVector;
    55.                     Time.timeScale = 0;
    56.                 }
    57.  
    58.             }
    59.         }
    60.  
    61.         if (Input.GetMouseButtonUp(0) && buttonClicked)
    62.         {
    63.             Destroy(currentGhost);
    64.             Instantiate(vehicle, specificVector, Quaternion.identity);
    65.             Time.timeScale = 1;
    66.         }
    67.     }
    68. }
    69.  
     
  9. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    Okay, I've uncovered my most recent mistake was not updating the buttonClicked boolean to false after releasing the mouse. That seems to have taken care of all the errors. Now the problem seems to be that the more units I add to the scene, the worse the performance is (the units dont want to track the mouse).

    It doesn't appear to be a performance problem , as my FPS is comfortably in the 70-90 range even as it continues to fail. Eventually the gui button stops functioning entirely, (WHY?!) and that's after only adding maybe 4 or 5 units to my scene.

    Animation continues to work smoothly, the button just...stops working.
     
  10. LordBlackwood

    LordBlackwood

    Joined:
    Aug 10, 2013
    Posts:
    26
    Oh, wow. Apparently it has to do with something it doesn't like on my prefab. I replaced it with stock cubes and it seems to be working well now. I'll have to narrow that down.

    Thanks for all your help.