Search code examples
c#unity-game-engineunity-editor

Issues with Unity Engine's Camera.ScreenToWorldPoint() in edit mode


I am programming an editor window in Unity so I can make board games quickly. My issue revolves around the ScreenToWorldPoint() making the script think the previous tile is in a completely different place than it actually is.

I have tried using the main camera instead, but that thinks the tile is in the center of the screen. I tried focusing the camera on the previous tile so I could use the main camera anyways, but this would make implementing a diverging path system more difficult down the line (and I don't like how it generally feels). I tried putting both the previous tile's position and and the mouse's position throught he screen to world point method and comparing them, but, as expected, that also didn't work. Finally, I also tried most every combination of different cameras in the OnSceneGUI(), and all of the different combinations failed to resolve the issue I am facing. Chat GPT is spitting out bad code, too, so I am very lost. I think the issue has something to do with the cameras viewing the world differently from the world coordinates the tiles exist in, making the the vectors incomparable. I think this, because dragging around the scene view changes the "center" of the world view from the sceneCam perspective, but not the position of the first tile (which always exists at the world coordinates' center. Below is my code for both the Stencil Script (where I think the problem is) and the Board Builder Script (in case this script is calling the Stencil Script in a way that causes issues). There are probably some unrelated bugs in the Board Builder script too, I am not done with it yet (but any advice on this script would be appreciated too!).

using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
using UnityEditor;
[ExecuteInEditMode]
public class StencilScript : MonoBehaviour
{
    private static EdgeCollider2D tileDist;
    private static Vector3 mousePos;
    private static Vector3 worldMousePos;
    private static Vector3 worldTilePos;
    private static Vector3 relativePos;
    private static Transform body;
    void OnEnable()
    {
        body = gameObject.transform;
        tileDist = gameObject.AddComponent<EdgeCollider2D>();
        tileDist.points = new Vector2[] {
            new Vector2(-1, -1),
            new Vector2(-1, -0.5f),
            new Vector2(-1, 0),
            new Vector2(-1, 0.5f),
            new Vector2(-1, 1),
            new Vector2(-0.5f, 1),
            new Vector2(0, 1),
            new Vector2(0.5f, 1),
            new Vector2(1, 1),
            new Vector2(1, 0.5f),
            new Vector2(1, 0),
            new Vector2(1, -0.5f),
            new Vector2(1, -1),
            new Vector2(0.5f, -1),
            new Vector2(0, -1),
            new Vector2(-0.5f, -1),
            new Vector2(-1, -1) };
        SceneView.duringSceneGui += OnSceneGUI;
    }
    private static void OnSceneGUI(SceneView sceneView)
    {
        Event e = Event.current;
        if (e.isMouse)
        {
            Camera cam = Camera.main;
            Camera sceneCam = sceneView.camera;
            mousePos = e.mousePosition;
            mousePos.y = cam.pixelHeight - mousePos.y;
            worldMousePos = sceneCam.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, sceneCam.nearClipPlane));
        }
    }
    private static Vector2 GetDrawLocation()
    {
        Vector2 exactPos = tileDist.ClosestPoint(worldMousePos);
        Vector2[] targets = tileDist.points;
        Vector2 drawPos = targets[0];
        for (int i = 1; i < targets.Length; i++)
        {
            float distanceA = Vector2.Distance(exactPos, targets[i]);
            float distanceB = Vector2.Distance(exactPos, drawPos);
            if (distanceA < distanceB)
            {
                drawPos = targets[i];
            }
        }
        return drawPos;
    }
    public void UpdateDrawLocation(GameObject tile)
    {
        if (tile != null)
        {
            Vector2 drawLocation = GetDrawLocation();
            tile.transform.position = new Vector3(drawLocation.x, drawLocation.y, tile.transform.position.z);
        }
    }
}

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class BoardBuilder : EditorWindow
{
    StencilScript _stencilScript;
    string boardName = "";
    GameObject active;
    GameObject stencil;
    GameObject board;
    GameObject nextToPlace;
    List<Transform> tiles = new List<Transform>();
    int tileCount;
    int nextSuitID;
    bool buildButton;
    string prefabFolder = "Assets/Prefabs/Tiles";
    private GameObject[] prefabs;
    private string[] tileNames;
    GameObject[] suitSpaces = new GameObject[4];
    int selected;
    static Vector2 mousePos;

    [MenuItem("Tools/Board Builder")]
    public static void ShowWindow() {
        GetWindow(typeof(BoardBuilder)); // GetWindow is from the EditorWindow Class
    }
    private void OnEnable() {
        LoadTiles();
        board = GameObject.FindGameObjectWithTag("Board");
        if (board != null)
        {
            tiles.Add(board.transform);
            for (int i = 0; i < board.transform.childCount; i++)
            {
                tiles.Add(board.transform.GetChild(i));
            }
            tileCount = board.transform.childCount + 1;
            active = tiles[tiles.Count - 1].gameObject;
        }
    }
    private void OnGUI()
    {
        boardName = EditorGUILayout.TextField("BoardName", boardName);
        if (GUILayout.Button("Create Board") && GameObject.FindGameObjectWithTag("Board") == null)
        {
            board = Instantiate(prefabs[0].gameObject);
            board.tag = "Board";
            board.name = boardName + " (Home)";
            board.transform.position = new Vector3(0, 0, 0);
            tileCount = 1;
        }
        tileCount = EditorGUILayout.IntField("Tile Count", tileCount);
        buildButton = GUILayout.Toggle(buildButton, "Build Mode");
        selected = GUILayout.SelectionGrid(selected, tileNames, 2);
        if (GUILayout.Button(new GUIContent("Refresh Tile Selection", "Checks for new tiles in the 'Tile' folder")))
        {
            LoadTiles();
        }
        GUILayout.Label("--------------\nMap?");

    }

    private void LoadTiles()
    {
        string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { prefabFolder });
        prefabs = new GameObject[guids.Length];
        tileNames = new string[guids.Length];
        for (int i = 0; i < guids.Length; i++)
        {
            string path = AssetDatabase.GUIDToAssetPath(guids[i]);
            prefabs[i] = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            tileNames[i] = prefabs[i].name;
        }
    }
    [ExecuteInEditMode]
    void Update()
    {
        if(board != null)
        {
            if (!buildButton)
            {
                if(active!= null && active.GetComponent<StencilScript>() != null)
                {
                    DestroyImmediate(active.GetComponent<EdgeCollider2D>());
                    DestroyImmediate(active.GetComponent<StencilScript>());
                }
                if (nextToPlace != null)
                {
                    ChangeTileInUse(-1);
                }
            }
            else
            {
                active = Selection.activeGameObject;
                if(active != null)
                {
                    if (active.GetComponent<StencilScript>() == null)
                    {
                        _stencilScript = active.AddComponent<StencilScript>();
                    }
                    if (nextToPlace == null || nextToPlace.GetComponent<Tile>().tileType != selected)
                    {
                        ChangeTileInUse(selected);
                    }
                    _stencilScript.UpdateDrawLocation(nextToPlace);
                    if (Input.GetMouseButtonDown(0))
                    {
                        PlaceTile();
                    }
                } 
            }
        }        
    }
    void ChangeTileInUse(int tileID)
    {
        DestroyImmediate(nextToPlace);
        if (tileID == -1)
        {
            nextToPlace = null;
        }
        else
        {
            nextToPlace = Instantiate(prefabs[tileID], board.transform);
            nextToPlace.GetComponent<Tile>().tileType = selected;
        }
    }
    private void PlaceTile()
    {
        UnityEngine.Debug.Log("place");
        DestroyImmediate(active.GetComponent<StencilScript>());
        DestroyImmediate(active.GetComponent<BoxCollider2D>());
        tiles[tileCount] = active.transform;
        Selection.activeGameObject = nextToPlace;
        active = nextToPlace;
        active.AddComponent<StencilScript>();
        nextToPlace = Instantiate(prefabs[selected].gameObject, board.transform);
        tileCount++;
    }
}


Solution

  • Figured it out after a bit of searching on the Unity Forums.

    ScreentToWorldPoint() does act weird in the Unity Editor. Instead, use the HandleUtility.GUIPointToWorldRay() to find the mouse position in the world view from the editor. To get the exact world position, you have to ask for the ray's position at a certain depth too, so I ask for its position at the depth of the previous tile (body.position.z).