The code snippet below allows you to attach a translation gizmo to a object to allow the user to move that object while the game is running. Works just like the unity translation gizmo.
using System;
using UnityEngine;
/// <summary>
/// The translation gizmo.
/// </summary>
public class TranslationGizmo : MonoBehaviour
{
#region Fields
/// <summary>
/// The camera reference used to determin distance to translation gizmo.
/// </summary>
public UnityEngine.Camera cameraReference;
/// <summary>
/// Used to determine how thick the envelope is around a axis line when attempting to click and drag it with the mouse.
/// </summary>
public float axisEnvelope = 0.04f;
/// <summary>
/// Determines the length of the cone.
/// </summary>
public float coneLength = 0.85f;
/// <summary>
/// Specifies how many segments the cone is made up of.
/// </summary>
public int coneSegments = 30;
/// <summary>
/// Determines the diameter of the cone.
/// </summary>
public float coneSize = 0.075f;
/// <summary>
/// Determines the length of the axis lines.
/// </summary>
public float lineLength = 0.13f;
/// <summary>
/// The line material used to render the lines.
/// </summary>
public Material lineMaterial;
/// <summary>
/// Determines the size of the square panning gizmos.
/// </summary>
public float panScale = 0.3f;
/// <summary>
/// Determines the transparency level of the axis plane quads.
/// </summary>
public float planeTransparency = 0.3f;
/// <summary>
/// The color that will be used for the X axis.
/// </summary>
public Color xAxisColor = Color.red;
/// <summary>
/// The color that will be used for the Y axis.
/// </summary>
public Color yAxisColor = Color.green;
/// <summary>
/// The color that will be used for the Z axis.
/// </summary>
public Color zAxisColor = Color.blue;
/// <summary>
/// Holds the button state used to determine if dragging has started.
/// </summary>
protected bool inputButtonState;
/// <summary>
/// Holds the relative mouse X position.
/// </summary>
protected float mouseAxisX;
/// <summary>
/// Holds the relative mouse Y position.
/// </summary>
protected float mouseAxisY;
/// <summary>
/// Holds the pre-calculated distance value from the camera to the game object.
/// </summary>
private float cameraDistance;
/// <summary>
/// The hit mouse down.
/// </summary>
private Vector3 hitMouseDown;
/// <summary>
/// The hit mouse move.
/// </summary>
private Vector3 hitMouseMove;
/// <summary>
/// Holds wheather or not the input axis has changed.
/// </summary>
private bool inputAxisChanged;
/// <summary>
/// Holds wheather or not the mouse if being dragged.
/// </summary>
private bool isDragging;
/// <summary>
/// Holds XY plane information.
/// </summary>
private Plane planeXY;
/// <summary>
/// Holds XZ plane information.
/// </summary>
private Plane planeXZ;
/// <summary>
/// Holds YZ plane information.
/// </summary>
private Plane planeYZ;
/// <summary>
/// Holds the previous input button state.
/// </summary>
private bool previousInputButtonState;
/// <summary>
/// Holds the rotation matrix used to determine at what rotation the gizmo will be drawn at.
/// </summary>
private Matrix4x4 rotationMatrix;
/// <summary>
/// Holds the value for the <see cref="WorldTranslation"/> property.
/// </summary>
[SerializeField]
private bool worldTranslation;
/// <summary>
/// Gets or sets a value indicating whether world translation is enabled.
/// </summary>
/// <value>
/// <c>true</c> if world translation is active; otherwise, <c>false</c> for local translation.
/// </value>
public bool WorldTranslation
{
get
{
return this.worldTranslation;
}
set
{
this.worldTranslation = value;
}
}
/// <summary>
/// Holds the type of axis movement is currently being made.
/// </summary>
private TypeOfMove typeMovementBeingMade;
#endregion
#region Public Events
/// <summary>
/// Occurs when position changed.
/// </summary>
public event EventHandler<Vector3EventArgs> PositionChanged;
#endregion
#region Enums
/// <summary>
/// Enum defining the types of axis movement.
/// </summary>
private enum TypeOfMove
{
/// <summary>
/// No axis movement.
/// </summary>
None,
/// <summary>
/// Movement along the X axis.
/// </summary>
XAxis,
/// <summary>
/// Movement along the
/// </summary>
X2Axis,
/// <summary>
/// Movement along the Y axis.
/// </summary>
YAxis,
/// <summary>
/// The y 2 axis.
/// </summary>
Y2Axis,
/// <summary>
/// Movement along the Z axis.
/// </summary>
ZAxis,
/// <summary>
/// The z 2 axis.
/// </summary>
Z2Axis,
/// <summary>
/// Movement along the XZ axis.
/// </summary>
XZAxis,
/// <summary>
/// Movement along the YZ axis.
/// </summary>
YZAxis,
/// <summary>
/// Movement along the XY axis.
/// </summary>
XYAxis,
};
#endregion
#region Public Methods and Operators
/// <summary>
/// Awake is called when the script instance is being loaded.
/// </summary>
public void Awake()
{
// if no material assigned setup a default material
if (this.lineMaterial == null)
{
#if !UNITY_5
this.lineMaterial =
new Material(
"Shader \"Lines/Colored Blended\" {" + "SubShader { Pass { " + " Blend SrcAlpha OneMinusSrcAlpha "
+ " ZWrite Off Cull Off Fog { Mode Off } ZTest Always" + " BindChannels {"
+ " Bind \"vertex\", vertex Bind \"color\", color }" + "} } }");
#else
// Unity has a built-in shader that is useful for drawing
// simple colored things.
var shader = Shader.Find("Hidden/Internal-Colored");
this.lineMaterial = new Material(shader);
this.lineMaterial.hideFlags = HideFlags.HideAndDontSave;
// Turn on alpha blending
this.lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
this.lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
// Turn backface culling off
this.lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
// Turn off depth writes
this.lineMaterial.SetInt("_ZWrite", 0);
#endif
}
}
/// <summary>
/// Update is called every frame, if the MonoBehaviour is enabled.
/// </summary>
public void Update()
{
if (cameraReference == null)
{
return;
}
// calculate distance once between the camera and the game objects position
var transformReference = this.transform;
var position = transformReference.position;
this.cameraDistance = Vector3.Distance(this.cameraReference.transform.position, position) * this.lineLength;
this.previousInputButtonState = this.inputButtonState;
this.GetInputButtonState();
// if the input button is released stop moving
if (!this.inputButtonState)
{
this.isDragging = false;
this.typeMovementBeingMade = TypeOfMove.None;
return;
}
// only handle dragging is already dragging or if input button state was pressed
if (this.isDragging || (!this.previousInputButtonState && this.inputButtonState))
{
// get mouse/input values
this.GetInputValues();
this.inputAxisChanged = Math.Abs(this.mouseAxisX) > float.Epsilon || Math.Abs(this.mouseAxisY) > float.Epsilon;
// update planes based on world or local translation
if (this.worldTranslation)
{
// update planes
this.planeXZ.SetNormalAndPosition(Vector3.up, position);
this.planeXY.SetNormalAndPosition(Vector3.forward, position);
this.planeYZ.SetNormalAndPosition(Vector3.right, position);
}
else
{
// update planes
this.planeXZ.SetNormalAndPosition(transformReference.up, position);
this.planeXY.SetNormalAndPosition(transformReference.forward, position);
this.planeYZ.SetNormalAndPosition(transformReference.right, position);
}
// cast a ray once
var ray = this.cameraReference.ScreenPointToRay(Input.mousePosition);
// handle axis translations
this.HandleXZ(ray);
this.HandleXY(ray);
this.HandleYZ(ray);
}
}
#endregion
#region Methods
/// <summary>
/// Get the state of the input button used to determine if dragging has occurred.
/// </summary>
protected virtual void GetInputButtonState()
{
this.inputButtonState = Input.GetMouseButton(0);
}
/// <summary>
/// Gets the X/Y input values
/// </summary>
protected virtual void GetInputValues()
{
this.mouseAxisX = Input.GetAxis("Mouse X");
this.mouseAxisY = Input.GetAxis("Mouse Y");
}
/// <summary>
/// Raises the <see cref="PositionChanged"/> event.
/// </summary>
/// <param name="previousPosition">
/// The previous Position.
/// </param>
/// <param name="position">
/// The position.
/// </param>
protected virtual void OnPositionChanged(Vector3 previousPosition, Vector3 position)
{
var handler = this.PositionChanged;
if (handler != null)
{
handler(this, new Vector3EventArgs(previousPosition, position));
}
}
/// <summary>
/// Uses OpenGL to draw a line for the axis.
/// </summary>
/// <param name="color">
/// The color to use when drawing the cone.
/// </param>
/// <param name="axis">
/// Defines axis that the cone will be drawn against.
/// </param>
/// <param name="axisU">
/// Represents the "X" axis to draw the base of the cone against.
/// </param>
/// <param name="axisV">
/// Represents the "Y" axis to draw the base of the cone against.
/// </param>
private void DrawAxis(Color color, Vector3 axis, Vector3 axisU, Vector3 axisV)
{
// scale the axis by the camera distance
axis = axis * this.cameraDistance;
// start drawing lines
GL.Begin(GL.LINES);
GL.Color(color);
GL.Vertex3(0, 0, 0);
GL.Vertex3(axis.x, axis.y, axis.z);
GL.End();
// draw the cone for the axis
this.DrawCone(axis, axisU, axisV, this.cameraDistance * this.coneSize, this.coneLength, color);
}
/// <summary>
/// Uses OpenGL to draw a cone for the axis.
/// </summary>
/// <param name="axis">
/// Defines axis that the cone will be drawn against.
/// </param>
/// <param name="axisU">
/// Represents the "X" axis to draw the base of the cone against.
/// </param>
/// <param name="axisV">
/// Represents the "Y" axis to draw the base of the cone against.
/// </param>
/// <param name="radius">
/// Specifies the radius for the base of the cone.
/// </param>
/// <param name="length">
/// Specifies how tall the cone is.
/// </param>
/// <param name="color">
/// The color to use when drawing the cone.
/// </param>
private void DrawCone(Vector3 axis, Vector3 axisU, Vector3 axisV, float radius, float length, Color color)
{
GL.Begin(GL.TRIANGLES);
GL.Color(color);
// pre-calculate the angle step for each cone segment
var angle = (2 * Mathf.PI) / this.coneSegments;
for (var i = 0; i <= this.coneSegments; i++)
{
var pt = axisU * Mathf.Cos(angle * i) * radius;
pt += axisV * Mathf.Sin(angle * i) * radius;
pt += axis * length;
GL.Vertex(pt);
pt = axisU * Mathf.Cos(angle * (i + 1)) * radius;
pt += axisV * Mathf.Sin(angle * (i + 1)) * radius;
pt += axis * length;
GL.Vertex(pt);
GL.Vertex(axis);
}
GL.End();
}
/// <summary>
/// Uses OpenGL to draw a square plane for the axis.
/// </summary>
/// <param name="size">
/// Defines size of the plane that will be drawn for the axis.
/// </param>
/// <param name="axisU">
/// Represents the "X" axis to draw the plane against.
/// </param>
/// <param name="axisV">
/// Represents the "Y" axis to draw the plane against.
/// </param>
/// <param name="color">
/// The color to use when drawing the plane.
/// </param>
private void DrawQuad(float size, Vector3 axisU, Vector3 axisV, Color color)
{
color.a = this.planeTransparency;
var pts = new Vector3[4];
pts[0] = Vector3.zero;
pts[1] = axisU * size;
pts[2] = (axisU + axisV) * size;
pts[3] = axisV * size;
GL.Begin(GL.QUADS);
GL.Color(color);
GL.Vertex(pts[0]);
GL.Vertex(pts[1]);
GL.Vertex(pts[2]);
GL.Vertex(pts[3]);
GL.End();
color.a = 1f;
GL.Begin(GL.LINES);
GL.Color(color);
GL.Vertex(pts[0]);
GL.Vertex(pts[1]);
GL.Vertex(pts[1]);
GL.Vertex(pts[2]);
GL.Vertex(pts[2]);
GL.Vertex(pts[3]);
GL.Vertex(pts[3]);
GL.Vertex(pts[0]);
GL.End();
}
/// <summary>
/// Start is called just before any of the Update methods is called the first time.
/// </summary>
public void Start()
{
if (this.cameraReference == null)
{
this.cameraReference = UnityEngine.Camera.main;
}
}
/// <summary>
/// Handles dragging along the XU plane.
/// </summary>
/// <param name="ray">
/// The reay used to determine if the XY plane was being interacted with.
/// </param>
private void HandleXY(Ray ray)
{
float enter;
this.planeXY.Raycast(ray, out enter);
var hit = ray.GetPoint(enter);
hit = this.rotationMatrix.inverse.MultiplyPoint(hit);
if (this.typeMovementBeingMade == TypeOfMove.None && hit.x > 0f && hit.x <= this.panScale * this.cameraDistance && hit.y > 0
&& hit.y <= this.panScale * this.cameraDistance)
{
this.typeMovementBeingMade = TypeOfMove.XYAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.XYAxis)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.z = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.x > 0f && hit.x <= this.cameraDistance
&& Mathf.Abs(hit.y) < this.axisEnvelope * this.cameraDistance)
{
// case x
this.typeMovementBeingMade = TypeOfMove.X2Axis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.X2Axis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.y = 0;
this.hitMouseMove.z = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.y > 0f && hit.y <= this.cameraDistance
&& Mathf.Abs(hit.x) < this.axisEnvelope * this.cameraDistance)
{
// case y
this.typeMovementBeingMade = TypeOfMove.YAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.YAxis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.z = 0;
this.hitMouseMove.x = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
}
/// <summary>
/// Handles dragging along the XZ plane.
/// </summary>
/// <param name="ray">
/// The reay used to determine if the XZ plane was being interacted with.
/// </param>
private void HandleXZ(Ray ray)
{
float enter;
this.planeXZ.Raycast(ray, out enter);
var hit = ray.GetPoint(enter);
hit = this.rotationMatrix.inverse.MultiplyPoint(hit);
if (this.typeMovementBeingMade == TypeOfMove.None && hit.x > 0f && hit.x <= this.panScale * this.cameraDistance && hit.z > 0
&& hit.z <= this.panScale * this.cameraDistance)
{
this.typeMovementBeingMade = TypeOfMove.XZAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.XZAxis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.y = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.x > 0f && hit.x <= this.cameraDistance
&& Mathf.Abs(hit.z) < this.axisEnvelope * this.cameraDistance)
{
// case x
this.typeMovementBeingMade = TypeOfMove.XAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.XAxis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.y = 0;
this.hitMouseMove.z = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.z > 0f && hit.z <= this.cameraDistance
&& Mathf.Abs(hit.x) < this.axisEnvelope * this.cameraDistance)
{
// case z
this.typeMovementBeingMade = TypeOfMove.ZAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.ZAxis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.y = 0;
this.hitMouseMove.x = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
}
/// <summary>
/// Handles dragging along the YZ plane.
/// </summary>
/// <param name="ray">
/// The reay used to determine if the YZ plane was being interacted with.
/// </param>
private void HandleYZ(Ray ray)
{
float enter;
this.planeYZ.Raycast(ray, out enter);
var hit = ray.GetPoint(enter);
hit = this.rotationMatrix.inverse.MultiplyPoint(hit);
if (this.typeMovementBeingMade == TypeOfMove.None && hit.z > 0 && hit.z <= this.panScale * this.cameraDistance && hit.y > 0
&& hit.y <= this.panScale * this.cameraDistance)
{
this.typeMovementBeingMade = TypeOfMove.YZAxis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.YZAxis)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.x = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.z > 0f && hit.z <= this.cameraDistance
&& Mathf.Abs(hit.y) < this.axisEnvelope * this.cameraDistance)
{
// case z
this.typeMovementBeingMade = TypeOfMove.Z2Axis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.Z2Axis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.y = 0;
this.hitMouseMove.x = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
if (this.typeMovementBeingMade == TypeOfMove.None && hit.y > 0f && hit.y <= this.cameraDistance
&& Mathf.Abs(hit.z) < this.axisEnvelope * this.cameraDistance)
{
// case y
this.typeMovementBeingMade = TypeOfMove.Y2Axis;
this.hitMouseDown = hit;
this.isDragging = true;
}
if (this.typeMovementBeingMade == TypeOfMove.Y2Axis && this.inputAxisChanged)
{
this.hitMouseMove = hit - this.hitMouseDown;
this.hitMouseMove.z = 0;
this.hitMouseMove.x = 0;
this.UpdatePosition(this.worldTranslation ? this.hitMouseMove : this.transform.localRotation * this.hitMouseMove);
}
}
/// <summary>
/// Called by unity when after camera has rendered the scene.
/// </summary>
private void OnRenderObject()
{
// set the material pass is a material has been specified
var material = this.lineMaterial;
if (material != null)
{
material.SetPass(0);
}
// push a matrix on to the stack
GL.PushMatrix();
this.rotationMatrix = this.transform.localToWorldMatrix;
// setup rotation matrix
if (this.worldTranslation)
{
this.rotationMatrix = Matrix4x4.TRS(this.transform.position, Quaternion.identity, Vector3.one);
}
else
{
this.rotationMatrix = Matrix4x4.TRS(this.transform.position, this.transform.localRotation, Vector3.one);
}
GL.MultMatrix(this.rotationMatrix);
// draw each axis
this.DrawAxis(this.xAxisColor, Vector3.right, Vector3.up, Vector3.forward);
this.DrawAxis(this.yAxisColor, Vector3.up, Vector3.right, Vector3.forward);
this.DrawAxis(this.zAxisColor, Vector3.forward, Vector3.right, Vector3.up);
// draw each plane quad
this.DrawQuad(this.panScale * this.cameraDistance, Vector3.forward, Vector3.up, this.xAxisColor);
this.DrawQuad(this.panScale * this.cameraDistance, Vector3.right, Vector3.forward, this.yAxisColor);
this.DrawQuad(this.panScale * this.cameraDistance, Vector3.right, Vector3.up, this.zAxisColor);
GL.PopMatrix();
}
/// <summary>
/// Updates the transform position and raises the <see cref="PositionChanged"/> event.
/// </summary>
/// <param name="value">
/// The value to add on to the existing transform position.
/// </param>
private void UpdatePosition(Vector3 value)
{
var position = this.transform.position;
var previousPosition = position;
position += value;
this.transform.position = position;
this.OnPositionChanged(previousPosition, position);
}
#endregion
}
///
/// Provides event arguments for the type.
///
public class Vector3EventArgs : EventArgs
{
#region Constructors and Destructors
///
/// Initializes a new instance of the class.
///
///
/// The previous value.
///
///
/// The current value.
///
public Vector3EventArgs(Vector3 previousValue, Vector3 value)
{
this.Value = value;
this.PreviousValue = previousValue;
}
///
/// Initializes a new instance of the class.
///
public Vector3EventArgs()
{
}
#endregion
#region Public Properties
///
/// Gets or sets a previous value.
///
public Vector3 PreviousValue { get; set; }
///
/// Gets or sets a value.
///
public Vector3 Value { get; set; }
#endregion
}