1
\$\begingroup\$

We got this SelectionManager Script from an Asset which allows us to select a building.

We wanted to track if the building is selected and then show a small menu above it. We managed to get it to work, but it was all in the SC_SelectionManager script and we wanted to separate it into two scripts.

So we wrote the micromenuScript which basicly just copied the working code. But we were not able to get the isSelected variable into the micromenuScript.

Can anyone please explain how to access a variable from one script in another?

We tried to get a reference to the selection manager script by calling microMenuScript.Find("_SelectionManager") as shown below, but got the following error:

'GameObject.Find(string)' cannot be accessed with an instance reference; qualify it with a type name instead.

I added both scripts.

SC_SelectionManager:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SC_SelectionManager : MonoBehaviour
{
    public Texture topLeftBorder;
    public Texture bottomLeftBorder;
    public Texture topRightBorder;
    public Texture bottomRightBorder;
    //public GameObject micromenu;
    public bool isSelected;
    
Texture2D _borderTexture;
Texture2D borderTexture
{
    get
    {
        if (_borderTexture == null)
        {
            _borderTexture = new Texture2D(1, 1);
            _borderTexture.SetPixel(0, 0, Color.white);
            _borderTexture.Apply();
        }

        return _borderTexture;
    }
}

bool selectionStarted = false;
Vector3 mousePosition1;

public static List<SC_Selectable> selectables = new List<SC_Selectable>();
List<int> selectedObjects = new List<int>();

void Start(){
  //micromenu.SetActive(false);
  isSelected = false;
}
// Update is called once per frame
void Update()
{
    // Begin selection
    if (Input.GetMouseButtonDown(0))
    {
        selectionStarted = true;
        mousePosition1 = Input.mousePosition;
    }
    // End selection
    if (Input.GetMouseButtonUp(0))
    {
        selectionStarted = false;
    }
    //Script für die Selection von Objecten
    if (selectionStarted)
    {
        // Detect which Objects are inside selection rectangle
        Camera camera = Camera.main;
        selectedObjects.Clear();
        //activeMenu(false);
        isSelected = false;
        Debug.Log("isSelected auf false gesetzt? =" + isSelected);
        for (int i = 0; i < selectables.Count; i++)
        {
            Bounds viewportBounds = GetViewportBounds(camera, mousePosition1, Input.mousePosition);
            if (viewportBounds.Contains(camera.WorldToViewportPoint(selectables[i].transform.position)))
            {
              selectedObjects.Add(i);
              //activeMenu(true);
              isSelected = true;
              Debug.Log("isSelected auf true gesetzt? =" + isSelected);
            }
        }
    }
}

/*private void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        selectedObjects.Add(0);
    }
}*/

void OnGUI()
{
    if (selectionStarted)
    {
        Rect rect = GetScreenRect(mousePosition1, Input.mousePosition);
        DrawScreenRectBorder(rect, 2, Color.cyan);
    }

    // Draw selection edges
    if(selectedObjects.Count > 0)
    {
        Camera camera = Camera.main;
        for (int i = 0; i < selectedObjects.Count; i++)
        {
            DrawSelectionIndicator(camera, selectables[selectedObjects[i]].GetObjectBounds());
        }
    }
}

void DrawScreenRectBorder(Rect rect, float thickness, Color color)
{
    // Top
    DrawBorderRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), color);
    // Left
    DrawBorderRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), color);
    // Right
    DrawBorderRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), color);
    // Bottom
    DrawBorderRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), color);
}

void DrawBorderRect(Rect rect, Color color)
{
    GUI.color = color;
    GUI.DrawTexture(rect, borderTexture);
    GUI.color = Color.white;
}

Rect GetScreenRect(Vector3 screenPosition1, Vector3 screenPosition2)
{
    // Move origin from bottom left to top left
    screenPosition1.y = Screen.height - screenPosition1.y;
    screenPosition2.y = Screen.height - screenPosition2.y;
    // Calculate corners
    var topLeft = Vector3.Min(screenPosition1, screenPosition2);
    var bottomRight = Vector3.Max(screenPosition1, screenPosition2);
    // Create Rect
    return Rect.MinMaxRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
}

Bounds GetViewportBounds(Camera camera, Vector3 screenPosition1, Vector3 screenPosition2)
{
    Vector3 v1 = camera.ScreenToViewportPoint(screenPosition1);
    Vector3 v2 = camera.ScreenToViewportPoint(screenPosition2);
    Vector3 min = Vector3.Min(v1, v2);
    Vector3 max = Vector3.Max(v1, v2);
    min.z = camera.nearClipPlane;
    max.z = camera.farClipPlane;

    Bounds bounds = new Bounds();
    bounds.SetMinMax(min, max);
    return bounds;
}

void DrawSelectionIndicator(Camera camera, Bounds bounds)
{
    Vector3 boundPoint1 = bounds.min;
    Vector3 boundPoint2 = bounds.max;
    Vector3 boundPoint3 = new Vector3(boundPoint1.x, boundPoint1.y, boundPoint2.z);
    Vector3 boundPoint4 = new Vector3(boundPoint1.x, boundPoint2.y, boundPoint1.z);
    Vector3 boundPoint5 = new Vector3(boundPoint2.x, boundPoint1.y, boundPoint1.z);
    Vector3 boundPoint6 = new Vector3(boundPoint1.x, boundPoint2.y, boundPoint2.z);
    Vector3 boundPoint7 = new Vector3(boundPoint2.x, boundPoint1.y, boundPoint2.z);
    Vector3 boundPoint8 = new Vector3(boundPoint2.x, boundPoint2.y, boundPoint1.z);

    Vector2[] screenPoints = new Vector2[8];
    screenPoints[0] = camera.WorldToScreenPoint(boundPoint1);
    screenPoints[1] = camera.WorldToScreenPoint(boundPoint2);
    screenPoints[2] = camera.WorldToScreenPoint(boundPoint3);
    screenPoints[3] = camera.WorldToScreenPoint(boundPoint4);
    screenPoints[4] = camera.WorldToScreenPoint(boundPoint5);
    screenPoints[5] = camera.WorldToScreenPoint(boundPoint6);
    screenPoints[6] = camera.WorldToScreenPoint(boundPoint7);
    screenPoints[7] = camera.WorldToScreenPoint(boundPoint8);

    Vector2 topLeftPosition = Vector2.zero;
    Vector2 topRightPosition = Vector2.zero;
    Vector2 bottomLeftPosition = Vector2.zero;
    Vector2 bottomRightPosition = Vector2.zero;

    for (int a = 0; a < screenPoints.Length; a++)
    {
        //Top Left
        if (topLeftPosition.x == 0 || topLeftPosition.x > screenPoints[a].x)
        {
            topLeftPosition.x = screenPoints[a].x;
        }
        if (topLeftPosition.y == 0 || topLeftPosition.y > Screen.height - screenPoints[a].y)
        {
            topLeftPosition.y = Screen.height - screenPoints[a].y;
        }
        //Top Right
        if (topRightPosition.x == 0 || topRightPosition.x < screenPoints[a].x)
        {
            topRightPosition.x = screenPoints[a].x;
        }
        if (topRightPosition.y == 0 || topRightPosition.y > Screen.height - screenPoints[a].y)
        {
            topRightPosition.y = Screen.height - screenPoints[a].y;
        }
        //Bottom Left
        if (bottomLeftPosition.x == 0 || bottomLeftPosition.x > screenPoints[a].x)
        {
            bottomLeftPosition.x = screenPoints[a].x;
        }
        if (bottomLeftPosition.y == 0 || bottomLeftPosition.y < Screen.height - screenPoints[a].y)
        {
            bottomLeftPosition.y = Screen.height - screenPoints[a].y;
        }
        //Bottom Right
        if (bottomRightPosition.x == 0 || bottomRightPosition.x < screenPoints[a].x)
        {
            bottomRightPosition.x = screenPoints[a].x;
        }
        if (bottomRightPosition.y == 0 || bottomRightPosition.y < Screen.height - screenPoints[a].y)
        {
            bottomRightPosition.y = Screen.height - screenPoints[a].y;
        }
    }

    GUI.DrawTexture(new Rect(topLeftPosition.x - 16, topLeftPosition.y - 16, 16, 16), topLeftBorder);
    GUI.DrawTexture(new Rect(topRightPosition.x, topRightPosition.y - 16, 16, 16), topRightBorder);
    GUI.DrawTexture(new Rect(bottomLeftPosition.x - 16, bottomLeftPosition.y, 16, 16), bottomLeftBorder);
    GUI.DrawTexture(new Rect(bottomRightPosition.x, bottomRightPosition.y, 16, 16), bottomRightBorder);
}

//Methode zum aktivieren des MicroMenues
  /* void activeMenu(bool state)
    {
      if(state == true)
        micromenu.SetActive(state);
      else
        micromenu.SetActive(state);

    }*/

}

microMenuScript:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class micromenuScript : MonoBehaviour
    {

      //public SC_SelectionManager selectionManager;
      public GameObject micromenu;
      
      void Start()
      {
        //
      }

      void Update()
      {
        menuVisibility();
      }

      public void menuVisibility()
      {
        if(micromenu.Find("_SelectionManager").GetComponent<SC_SelectionManager>().isSelected == true)
        {
          micromenu.SetActive(true);
        }
        else
        {
          micromenu.SetActive(false);
        }
      }
}

Thanks for your time.

Have great day.

\$\endgroup\$
1
  • \$\begingroup\$ Glad I could help, all the best \$\endgroup\$
    – Basic
    Commented Nov 18, 2020 at 23:59

1 Answer 1

1
\$\begingroup\$

Background

Normally when a class has variables, every time you make a new instance of the class, it gets its own copies of each variable.

(You can create two Animal classes and give each one a different Name)

A static variable is different. Instead of each instance having its own copy of the variable, they all share the same one.

So if you set animal1.StaticName, you'd change animal2.StaticName as well. More to the point, you don't even need to make an animal, you can just set Animal.StaticName directly.

So... A static variable can be read/set (and static methods can be executed) even if you haven't created an instance of the class.

Your case

.Find() is a static method of GameObject. that means that microMenu.Find() is actually calling GameObject.Find(). (MonoBehaviour is a type of GameObject)

.Net is complaining you're using an "instance reference" (microMenu) when you should use the type name (GameObject) instead.

So change your code to GameObject.Find() and you should be fine.

Finding children

Digging a step deeper... If the reason you're trying to Find on a specific instance is that you want to find a child of that instance, you want to look at microMenu.transform.Find().

GameObject.Find() will search the whole scene, microMenu.transform.Find() will search immediate children of microMenu (and is considerably faster).

Cross-script access

Ideally, you want to use something like this to store a reference to each script

[In this example, Script1 is unaware of Script2 and Script2 can control/drive Script1, but you can apply the same technique in both directions should you wish.]

Script1:
public class Script1 : MonoBehaviour {
    public string status = String.Empty;
    public void Test() {
        Debug.LogFormat("Status: {0}", status);
    }
}
Script2:
public class Script2 : MonoBehaviour {
    Script1 otherScript;
    void Awake() {
        //If on same object:
        otherScript = this.GetComponent<Script1>();

        //If on object "_OtherObj"...
        //Will complaing if _OtherObj doesn't exist
        otherScript = GameObject.Find("_OtherObj").GetComponent<Script1>();

        //In either case, check it worked...

        if(otherScript == null) {Debug.LogWarning("Couldn't find otherScript");}
    }

    void Start() {
        otherScript.status = "First test";
        otherScript.Test();
        otherScript.status = "Second test";
        otherScript.Test();
    }
}

You could get away without looking for a specific reference and instead make Sript1 status public static string status.

In that case, script2 (or anything else) can just refer to Script1.status at any time.

The problem with this approach is that it's outside Unity's purview. Eg If the scene changes, the old value will be kept hanging around, possibly pointing at invalid/stale data.

In addition, certain actions taken by unity (like reloading the scene after a compile) will reload the runtime which will reset the value.

So it's not really recommended unless you're using it very defensively (always checking the value is present/credible before use).

Things to note

Any script variables you make public (like status above) will be surfaced in the Unity editor's Inspector when you select the object.

Any value you set through the editor will be saved with the scene, and applied to your scripts after they're created, before Awake() is called.

Some final thoughts...

Searching for objects is expensive. Do it as infrequently as possible (eg here it's done once on object load).

Also, you want to do as little as possible in each update loop. Instead of having every object poll to see if it's selected and hide/show a menu, you'd be better to handle the select/unselect events to toggle the menu directly.

That way, your code runs once when needed, rather than dozens/hundreds of times every frame.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .