Teleporting an Object in Editor to the First Collision Pointed by the Mouse

Decided to give another tool I use daily.

Basically, it teleports the selection to the first collision encountered by the mouse. Very useful when moving stuff around. Invoked with CTRL+T.

There’s a small utility used to get the mouse position outside a SceneView context;

/// <summary>
/// This is used to find the mouse position when it's over a SceneView.
/// Used by tools that are menu invoked.
/// </summary>
[InitializeOnLoad]
public class MouseHelper : Editor
{
    private static Vector2 position;

    public static Vector2 Position
    {
        get { return position; }
    }

    static MouseHelper()
    {
        SceneView.onSceneGUIDelegate += UpdateView;
    }

    private static void UpdateView(SceneView sceneView)
    {
        if (Event.current != null)
            position = new Vector2(Event.current.mousePosition.x + sceneView.position.x, Event.current.mousePosition.y + sceneView.position.y);
    }
}

Finally, the code to teleport stuff around;

/// <summary>
/// Teleport the selection to the first collider found by the mouse.
/// </summary>
public class Teleport
{
    [MenuItem("Tool/Teleport %t")]
    public static void TeleportTool()
    {
        if (Selection.gameObjects.Length == 0)
            return;

        SceneView view = EditorWindow.GetWindow<SceneView>();

        Vector2 position = MouseHelper.Position;

        if (position.x < view.position.x || position.x > view.position.x + view.position.width)
            return;

        if (position.y < view.position.y + SceneView.kToolbarHeight || position .y > view.position.y + view.position.height)
            return;

        Vector3 offset = new Vector3((position.x - view.position.x) / view.position.width, ((view.position.height + view.position.y) - position.y) / view.position.height, 0);
        Ray ray = view.camera.ViewportPointToRay(offset);

        RaycastHit[] hits = Physics.RaycastAll(ray);

        if (hits.Length == 0)
            return;

        RaycastHit? best = null;
        foreach (RaycastHit hit in hits)
        {
            if (best == null  !Selected(hit))
                best = hit;
            else if (best != null  hit.distance < ((RaycastHit)best).distance  !Selected(hit))
                best = hit;
        }

        if (best != null)
        {
            Undo.RegisterUndo(Selection.gameObjects, "Teleport");
            foreach (GameObject o in Selection.gameObjects)
            {
                o.transform.position = ((RaycastHit)best).point;
            }
        }
    }

    private static bool Selected(RaycastHit hit)
    {
        foreach (GameObject o in Selection.gameObjects)
        {
            if ((hit.collider != null  o == hit.collider.gameObject) ||
                (hit.rigidbody != null  o == hit.rigidbody.gameObject))
                return true;
        }

        return false;
    }
}

Thanx a lot for sharing, bro! Works fine and really usefull! :slight_smile:

Best unearthing!!

Well… the script doesnt work with unity 2018.3.6f1, someone now why?
Unity tell that he doesn’t know “SceneView.kToolbarHeight” and some “if” miss “&&” or “||” (but i don’t know one or other)

Someone can help me please? :slight_smile:

Old post, but useful script.

I took the liberty to debug the code to current UnityEditor api and fix what was not being displayed correctly in code snippets above.

MouseHelper.cs (with updated SceneView.duringSceneGui) :

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

/// <summary>
/// This is used to find the mouse position when it's over a SceneView.
/// Used by tools that are menu invoked.
/// </summary>
[InitializeOnLoad]
public class MouseHelper : Editor
{
    private static Vector2 position;
    public static Vector2 Position
    {
        get { return position; }
    }

    static MouseHelper()
    {
        SceneView.duringSceneGui += UpdateView;
    }

    private static void UpdateView(SceneView sceneView)
    {
        if (Event.current != null)
            position = new Vector2(Event.current.mousePosition.x + sceneView.position.x, Event.current.mousePosition.y + sceneView.position.y);
    }
}

EditorTeleport.cs (with updated sceneview width and height, fixed condition operators) :

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

/// <summary>
/// Teleport the selection to the first collider found by the mouse.
/// </summary>
public class EditorTeleport
{
    [MenuItem("MyTools/Teleport %²")]       //ctrl + ² shortcut in Unity Shortcuts
    public static void TeleportTool()
    {
        if (Selection.gameObjects.Length == 0)
            return;

        SceneView view = EditorWindow.GetWindow<SceneView>();

        Vector2 position = MouseHelper.Position;

        if (position.x < view.position.x || position.x > view.position.x + view.position.width)
            return;

        if (position.y < view.position.y || position.y > view.position.y + view.position.height)
            return;

        Vector3 offset = new Vector3((position.x - view.position.x) / view.position.width, ((view.position.height + view.position.y) - position.y) / view.position.height, 0);
        Ray ray = view.camera.ViewportPointToRay(offset);

        RaycastHit[] hits = Physics.RaycastAll(ray);

        if (hits.Length == 0)
            return;

        RaycastHit? best = null;
        foreach(RaycastHit hit in hits)
        {
            if (best == null || Selected(hit))
                best = hit;
            else if (best != null && hit.distance < ((RaycastHit)best).distance && !Selected(hit))
                best = hit;
        }

        if(best != null)
        {
            Undo.RegisterCompleteObjectUndo(Selection.gameObjects, "Teleport");
            foreach (GameObject o in Selection.gameObjects)
            {
                o.transform.position = ((RaycastHit)best).point;
            }
        }
    }

    private static bool Selected(RaycastHit hit)
    {
        foreach (GameObject o in Selection.gameObjects)
        {
            if((hit.collider != null && o == hit.collider.gameObject) || (hit.rigidbody != null && o == hit.rigidbody.gameObject))
                return true;
        }

        return false;
    }
}

I changed the shortcut to be ctrl + ².
Make sure to add the shortcut to Unity Shortcuts in editor as it is how it is handled now since the original post.
Unity Shortcuts are really easy to add and edit and check conflicts.
Once the script has compiled, you will easily find the shortcut by searching “MyTools/Teleport”.

I will customize the script to make it so it teleports my player (without selection) if there is one in the scene on pressing ‘²’.

Thanks LightStriker for original code.