Skip to content

Instantly share code, notes, and snippets.

@ByronMayne
Last active May 5, 2018 16:01
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ByronMayne/a22d41179a610aa4a1a06779c0434858 to your computer and use it in GitHub Desktop.
Save ByronMayne/a22d41179a610aa4a1a06779c0434858 to your computer and use it in GitHub Desktop.
A int version of a Vector2 in Unity. Supports explicit conversion between the two types as well as a few helpful helper functions. The second script is a property drawer that supports panning of values and right clicking to reset the value to zero (taking from 3Ds Max).
using UnityEngine;
using System;
[Serializable]
public struct Point
{
[SerializeField]
public int x;
[SerializeField]
public int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// Gets the value at an index.
/// </summary>
/// <param name="index">The index you are trying to get.</param>
/// <returns>The value at that index.</returns>
public int this[int index]
{
get
{
int result;
if(index != 0)
{
if(index != 1)
{
throw new IndexOutOfRangeException("Index " + index.ToString() + " is out of range." );
}
result = y;
}
else
{
result = x;
}
return result;
}
set
{
if (index != 0)
{
if (index != 1)
{
throw new IndexOutOfRangeException("Index " + index.ToString() + " is out of range.");
}
y = value;
}
else
{
x = value;
}
}
}
/// <summary>
/// Sets the x and y components of an existing Point
/// </summary>
/// <param name="x">The new x value</param>
/// <param name="y">The new y value</param>
public void Set(int x, int y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// Shorthand for writing new Point(0,0).
/// </summary>
public static Point zero
{
get
{
return new Point(0, 0);
}
}
/// <summary>
/// Shorthand for writing new Point(1,1).
/// </summary>
public static Point one
{
get
{
return new Point(1, 1);
}
}
public static explicit operator Vector2(Point point)
{
return new Vector2((float)point.x, (float)point.y);
}
public static explicit operator Point(Vector2 vector2)
{
return new Point((int)vector2.x, (int)vector2.y);
}
public static Point operator +(Point lhs, Point rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
return lhs;
}
public static Point operator -(Point lhs, Point rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
return lhs;
}
public static Point operator *(Point lhs, Point rhs)
{
lhs.x *= rhs.x;
lhs.y *= rhs.y;
return lhs;
}
public static Point operator /(Point lhs, Point rhs)
{
lhs.x /= rhs.x;
lhs.y /= rhs.y;
return lhs;
}
public static bool operator ==(Point lhs, Point rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.x;
}
public static bool operator !=(Point lhs, Point rhs)
{
return lhs.x != rhs.x || lhs.y != rhs.x;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = (int) 2166136261;
hash = (hash * 16777619) ^ x;
hash = (hash * 16777619) ^ y;
return hash;
}
}
public override bool Equals(object other)
{
if(!(other is Point))
{
return false;
}
Point point = (Point)other;
return x == point.x && y == point.y;
}
public override string ToString()
{
return string.Join(", ", new string[] { x.ToString(), y.ToString() });
}
}
using UnityEngine;
using UnityEditor;
/// <summary>
/// Draws a unique property drawer for <see cref="Point"/> which
/// draws the values in line. It also adds support for panning the values
/// and to right click the value to reset it to zero.
/// </summary>
[CustomPropertyDrawer(typeof(Point))]
public class PointPropertyDrawer : PropertyDrawer
{
private const float FIELD_LABEL_WIDTH = 15f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Rect labelRect = position;
labelRect.width = EditorGUIUtility.labelWidth;
GUI.Label(labelRect, label);
Rect contentRect = position;
contentRect.x += labelRect.width;
contentRect.width -= labelRect.width;
// Get our properties
SerializedProperty xValue = property.FindPropertyRelative("x");
SerializedProperty yValue = property.FindPropertyRelative("y");
Rect xContentRect = contentRect;
xContentRect.width /= 2f;
Rect yContentRect = xContentRect;
yContentRect.x += xContentRect.width;
float contentWidth = xContentRect.width;
DrawContent(xValue, xContentRect, contentWidth);
DrawContent(yValue, yContentRect, contentWidth);
HandleDrag(property);
}
/// <summary>
/// Used to allow the user to slide the values when clicking the label.
/// </summary>
private static void HandleDrag(SerializedProperty property)
{
if (Event.current.type == EventType.MouseDrag)
{
string propertyName = DragAndDrop.GetGenericData("PointEditorDrag") as string;
SerializedProperty modifiedProperty = property.FindPropertyRelative(propertyName);
// We could not find the property so something went wrong. Force quit it.
if (modifiedProperty == null)
{
// Stops the drag events.
DragAndDrop.AcceptDrag();
}
else
{
// We modify our value by the mouses delta movement.
modifiedProperty.intValue += Event.current.delta.x > 0f ? 1 : -1;
}
Event.current.Use();
}
// We have stopped dragging and dropping.
if (Event.current.type == EventType.MouseUp)
{
// Clear our data.
DragAndDrop.PrepareStartDrag();
}
}
private void DrawContent(SerializedProperty propertValue, Rect rect, float contentWidth)
{
// We are about to draw a label we want to set our width.
rect.width = FIELD_LABEL_WIDTH;
// Changes the mouse icon to an left right arrow to show the user they can slide the values.
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeLeftRight);
// If we Context Click (right click) and are inside the rect of the label.
if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition))
{
// Wipe the focus of the controls.
ClearFocus();
// Force our value to zero.
propertValue.intValue = 0;
// Use the event so no one else can.
Event.current.Use();
}
// We clicked on the label rect.
else if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
{
// Wipe the focus of the controls.
ClearFocus();
// Start the drag function
DragAndDrop.PrepareStartDrag();
// We use the property name as our generic data so we can assign the property later.
DragAndDrop.SetGenericData("PointEditorDrag", propertValue.name);
// Use the current event.
Event.current.Use();
}
// Draw the label
GUI.Label(rect, propertValue.displayName);
// Push the rect to the left by the labels width.
rect.x += FIELD_LABEL_WIDTH;
// Reset to the correct width.
rect.width = contentWidth - FIELD_LABEL_WIDTH;
// Draw our int value box.
propertValue.intValue = EditorGUI.IntField(rect, propertValue.intValue);
}
private void ClearFocus()
{
// Clear the focus of the mouse (Unity will not let you change the value if it's in focus).
GUIUtility.hotControl = -1;
// Clear the keyboard focus.
GUIUtility.keyboardControl = -1;
}
}
@ByronMayne
Copy link
Author

Example Gif of the editor. The values being reset to zero is done by right clicking the label.
example

@ByronMayne
Copy link
Author

Updated the GetHashCode() function because it would have the following issue.

Point x = new Point(0, 1);
Point y = new Point(1, 0);

int xHash = x.GetHashCode();
int yHash = y.GetHashCode();

// Would be true. 
bool sameHash = xHash == yHash;

Both hashes would be the same which is in correct since they have two different values.

@gresolio
Copy link

// Typo in the == and != operators:
return lhs.x == rhs.x && lhs.y == rhs.x;

// Fix, braces for readability:
return (lhs.x == rhs.x) && (lhs.y == rhs.y);

@GhatSmith
Copy link

Nice script. Just found a little mistake. Drag is not working if you have multiple Point fields in inspector. It's due to the "Event.current.Use();" in HandleDrag method. You should pass rect position to HandleDrag and check if it contains the mouse position before doing anything ;).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment