I found the solution of “just throw a mesh collider on it” to not be acceptable.
However, it is probably the right call for anything runtime. If you want something for the editor, use this. (Caveats included: This was designed for a 2d system to enable dragging in the 2d plane from a 3d camera view.)
Hopefully, someone will take the information here and distill it into a reusable form:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
[CustomEditor(typeof(BaseUnit), true)]
public class ClickBaseUnitEditor : Vexe.Editor.Editors.BaseBehaviourEditor
{
public static bool blockingMouseInput = false;
public static Vector3 offset = Vector3.zero;
public const string SuspendedBuff = "_EDITOR_Suspended";
public void SuspendUnit(BaseUnit unit)
{
if ( Application.isPlaying )
{ /// custom stuff
}
}
public void ReleaseUnit(BaseUnit unit)
{/// custom stuff
}
public static Ray GetMouseClickRay()
{
Event e = Event.current;
var camera = SceneView.lastActiveSceneView.camera;
var mousePosition = e.mousePosition;
mousePosition.y = camera.pixelHeight - mousePosition.y;
var ray = camera.ScreenPointToRay(mousePosition);
return ray;
}
public static bool MouseClickRaycheckSpineObjects(out IList<BaseUnit> spineUnitsFound, out IList<Vector3> hits)
{
var ray = GetMouseClickRay();
var spineObjs = Transform.FindObjectsOfType<SkeletonAnimation>();
spineUnitsFound = new List<BaseUnit>();
hits = new List<Vector3>();
//Debug.DrawRay(ray.origin, ray.direction * 80f + new Vector3(0.1f,0f,0f), Color.blue,10);
foreach ( var spineGameObj in spineObjs )
{
var root = spineGameObj.transform.position;
var intersectFound = false;
var meshFilter = spineGameObj.GetComponent<MeshFilter>();
if ( meshFilter != null )
{
var mesh = meshFilter.sharedMesh;
for ( int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex ++ )
{
var triangles = mesh.GetTriangles(subMeshIndex);
for ( int iTriBegin = 0; iTriBegin < triangles.Length; iTriBegin += 3 )
{
Vector3 a, b, c;
a = mesh.vertices[triangles[iTriBegin]];
b = mesh.vertices[triangles[iTriBegin+1]];
c = mesh.vertices[triangles[iTriBegin+2]];
a = spineGameObj.transform.TransformPoint(a);
b = spineGameObj.transform.TransformPoint(b);
c = spineGameObj.transform.TransformPoint(c);
if ( Intersect(a, b, c, ray) )
{
intersectFound = true;
break;
}
}
if ( intersectFound ) break;
}
}
if ( intersectFound )
{
var baseUnit = spineGameObj.GetComponentInParent<BaseUnit>();
if ( baseUnit )
{
var plane = new Plane( Vector3.back, spineGameObj.transform.position );
Vector3 p = Vector3.zero;
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
p = ray.GetPoint(rayDistance);
}
if ( baseUnit.gameObject == Selection.activeGameObject )
{
spineUnitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
spineUnitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
}
if ( spineUnitsFound.Count > 0 )
return true;
return false;
}
public static bool MouseClickRaycheckBaseUnit(out IList<BaseUnit> unitsFound, out IList<Vector3> hits)
{
var ray = GetMouseClickRay();
var coll = Physics2D.RaycastAll(ray.origin, ray.direction, Mathf.Infinity);
//Debug.DrawRay(ray.origin, ray.direction * 80f + new Vector3(0.1f,0f,0f), Color.blue,10);
unitsFound = new List<BaseUnit>();
hits = new List<Vector3>();
foreach ( var c in coll )
{
var baseUnit = c.collider.gameObject.GetComponentInParent<BaseUnit>();
if ( baseUnit )
{
var plane = new Plane( Vector3.back, baseUnit.transform.position);
Vector3 p = Vector3.zero;
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
p = ray.GetPoint(rayDistance);
}
if ( baseUnit.gameObject == Selection.activeGameObject )
{
unitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
unitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
IList<BaseUnit> spineUnitsFound;
IList<Vector3> spineHits;
var anythingFound = MouseClickRaycheckSpineObjects( out spineUnitsFound, out spineHits );
if ( anythingFound )
{
for ( int i = 0; i < spineUnitsFound.Count; i ++ )
{
var baseUnit = spineUnitsFound*;*
var p = spineHits*;*
if ( unitsFound.Contains(baseUnit) )
continue;
if ( baseUnit.gameObject == Selection.activeGameObject )
{
unitsFound.Insert(0, baseUnit);
hits.Insert(0, p);
}
else
{
unitsFound.Add(baseUnit);
hits.Add( p);
}
}
}
if ( unitsFound.Count > 0 )
return true;
return false;
}
public static bool MouseClickRaycheckAgainstBaseUnitPlane(BaseUnit b, out Vector3 intersect)
{
intersect = Vector3.zero;
var plane = new Plane( Vector3.back, b.transform.position);
/*
var camera = SceneView.lastActiveSceneView.camera;
var mousePosition = Event.current.mousePosition;
mousePosition.y = camera.pixelHeight - mousePosition.y;
var ray2 = camera.ScreenPointToRay(mousePosition);
*/
var ray = GetMouseClickRay();
float rayDistance;
if ( plane.Raycast(ray, out rayDistance )) {
intersect = ray.GetPoint(rayDistance);
return true;
}
return false;
}
private void OnSceneGUI( )
{
Event e = Event.current;
var controlID = GUIUtility.GetControlID(FocusType.Passive);
var type = e.GetTypeForControl(controlID );
var selection = Selection.activeGameObject;
switch ( type )
{
case EventType.MouseDown:
GUIUtility.hotControl = controlID;
//Debug.Log(“Mouse Down”);
IList unitsFound;
IList hits;
var anythingFound = MouseClickRaycheckBaseUnit(out unitsFound, out hits);
if ( anythingFound && unitsFound[0].gameObject == Selection.activeGameObject )
{
Debug.Log(“You clicked on:” + unitsFound[0]);
offset = unitsFound[0].transform.position - hits[0];
blockingMouseInput = true;
e.Use();
}
else if ( anythingFound )
{
Selection.activeGameObject = unitsFound[0].gameObject;
}
else
{
Selection.activeObject = null;
}
break;
case EventType.MouseMove:
case EventType.MouseDrag:
if ( blockingMouseInput )
{
if ( selection )
{
var baseUnit = selection.gameObject.GetComponentInParent();
Vector3 p;
if ( MouseClickRaycheckAgainstBaseUnitPlane(baseUnit, out p) )
{
//Debug.Log("Dragging to "+p);
baseUnit.gameObject.transform.position = p + offset;
SuspendUnit( baseUnit );
e.Use();
}
//Debug.Log("Mouse " + type + " | " + e.mousePosition);
}
}
break;
case EventType.MouseUp:
GUIUtility.hotControl = 0;
//Debug.Log(“Mouse Up”);
blockingMouseInput = false;
break;
}
}
///
/// Checks if the specified ray hits the triagnlge descibed by p1, p2 and p3.
/// Möller–Trumbore ray-triangle intersection algorithm implementation.
///
/// Vertex 1 of the triangle.
/// Vertex 2 of the triangle.
/// Vertex 3 of the triangle.
/// The ray to test hit for.
/// true when the ray hits the triangle, otherwise false
public static bool Intersect(Vector3 p1, Vector3 p2, Vector3 p3, Ray ray)
{
// Vectors from p1 to p2/p3 (edges)
Vector3 e1, e2;
Vector3 p, q, t;
float det, invDet, u, v;
//Find vectors for two edges sharing vertex/point p1
e1 = p2 - p1;
e2 = p3 - p1;
// calculating determinant
p = Vector3.Cross(ray.direction, e2);
//Calculate determinat
det = Vector3.Dot(e1, p);
//if determinant is near zero, ray lies in plane of triangle otherwise not
if (det > -Mathf.Epsilon && det < Mathf.Epsilon) { return false; }
invDet = 1.0f / det;
//calculate distance from p1 to ray origin
t = ray.origin - p1;
//Calculate u parameter
u = Vector3.Dot(t, p) * invDet;
//Check for ray hit
if (u < 0 || u > 1) { return false; }
//Prepare to test v parameter
q = Vector3.Cross(t, e1);
//Calculate v parameter
v = Vector3.Dot(ray.direction, q) * invDet;
//Check for ray hit
if (v < 0 || u + v > 1) { return false; }
if ((Vector3.Dot(e2, q) * invDet) > Mathf.Epsilon)
{
//ray does intersect
return true;
}
// No hit at all
return false;
}
}