Draw 2D Physics Shapes in Unity3D
In this tutorial I will show you how to draw and manipulate basic 2D physics shapes with your cursor using Unity, such as rectangles, circles and triangles. No additional external dependencies are required (tested with 2017.2, but should also work with 5.x).
Background
Back in 2008, I had loads of fun playing IncrediBots. For the uninitiated, the game is very similar to Phun (now Algodoo); the game involved drawing basic physics shapes, placing rotating/sliding joints on them, and controlling motor joints with keyboard input. The game featured several puzzles and a rating system for published contraptions.
Here’s a few examples of things that you could make (Credit: uraniu235):
So let’s do the same thing in Unity! Conveniently, Unity has excellent support for 2D physics, so implementing a basic prototype is fairly simple to do.
There are 4 parts to this post:
- Drawing 2D polygon meshes
- Specifying Points with the Cursor
- Adding 2D Physics
- Drawing Multiple Shapes
This tutorial will implement drawing rectangles, circles, and triangles.
Drawing 2D Polygon Meshes
To draw a mesh programmatically, Unity requires an array of its vertices and triangles. Unity allows each vertex to be colored differently, so we will demonstrate this by feeding our mesh random colors. To display the mesh, we apply it to a GameObject’s MeshFilter
component.
Below is a script that can be added to any empty object to draw the shape in the image above.
Specifying vertices is easy, as we can list them in an array of Vector2
s. The vertices2D
array defines an I-shape. A small complication is that Unity wants an array of Vector3
s, so we need to convert them. System.Array.ConvertAll
is handy for this.
To find the triangles in a 2D polygon’s list of points, we can use a triangulation algorithm. I copied the well-suited Triangulator.cs
class specified in the Unity3D wiki and dropped it in the project. There’s nothing too fancy here; just give the object a list of points and it will give back the indices of each triangle.
The next step involves creating an array of colors, one for each vertex. Here they are initialized to random colors with Random.ColorHSV()
using Linq syntax.
The last step is to create the mesh from the vertices, triangles, and color data and apply the mesh to a MeshFilter
to draw it. Note that it is a good practice to call mesh.RecalculateBounds()
and mesh.RecalculateNormals()
to ensure the mesh behaves as it should.
If we attach the script to an empty GameObject
, we should see an I-shaped polygon with lots of colors (see above). Check out commit 1 - draw simple polygon mesh to view the results in Unity.
Specifying Points with the Cursor
To capture input from the mouse and create shapes, we will create a controller that handles this. Its job is to create new shapes if no shapes are being drawn, update shapes if they require additional input, and set shapes loose if they are complete. To keep things simple, a script will collect new vertices whenever the left mouse button is released (click + drag is left as an exercise to the reader).
Let’s start with rectangles. To draw one, we can specify its two opposite corner vertices with mouse input.
Drawing Rectangles
To aid with simple operations on lists of vectors, we will create a Util.cs
script defining a few handy operations.
Next, create an empty Rectangle
prefab and attach a script called DrawRectangle.cs
. The rectangle prefab is expected have a MeshFilter
to draw its mesh (make sure to add a material to properly display the mesh), and LineRenderer
to draw a rectangle outline.
DrawRectangle.cs
defines two public functions:
AddVertex(Vector2 vertex)
adds a new vertex to its internal list of two vertices and callsUpdateShape
.- If two vertices have been added,
UpdateShape(Vector2 newVertex)
replaces its second vertex with a new one. The four corners are extrapolated from the two vertices to update the mesh and line renderer. The shape is automatically redrawn by Unity every time this function is called.
The third function, RectangleMesh(Vector2 v0, Vector2 v1, Color fillColor)
, serves to generate the rectangle mesh similar to the I-shaped polygon in the previous section.
One thing to note is that the rectangle’s transform set at the midpoint (centroid) of the mesh. The mesh’s vertices are relative to this center point. This will make some future calculations nicer (e.g., attaching a box collider). Also note that the script exposes a public FillColor
, which fills the generated mesh with the given color.
Draw Algorithm
The basic algorithm for drawing a rectangle is as follows.
- If the left mouse button is released and no rectangle is being updated, create a new rectangle with its two opposite corner vertices at the current mouse position. Indicate that the rectangle is being updated.
- If a rectangle is being updated, update the rectangle’s last corner vertex to be at the current mouse position.
- If the left mouse button is released and a rectangle is being updated, stop updating it.
Below we define new script called DrawController.cs
. Its job is to collect mouse input and send mouse coordinates to the currently active rectangle GameObject
. In essence, it specifies the draw algorithm above.
The property CurrentShapeToDraw
holds the current rectangle, and IsDrawingShape
is the condition that allows vertices to be added to the current shape.
Check out commit 2 - draw rectangle to view the results in Unity.
Adding 2D Physics
From what is implemented, adding physics to these rectangles is simple. First, attach a Rigidbody2D
and a BoxCollider2D
to the rectangle prefab. The biggest change is in the DrawRectangle.cs
script:
- Add a public
SimulatingPhysics
property that when set, simulates physics with theRigidbody2D
. Otherwise, the shape is set to static so that it stops moving and other shapes may still interact with it. - Update the
BoxCollider2D
’s bounding box after we update the mesh.
Lastly, the DrawController.cs
script sets CurrentShapeToDraw.SimulatingPhysics
when the current rectangle is complete and ready to be released.
And voila, we have physics! Just make sure to add a static platform to prevent rectangles from falling through the scene. Check out commit 3 - add rectangle physics to view the results in Unity.
Drawing Multiple Shapes
By extension, drawing circles, triangles, and arbitrary polygons should be very similar to drawing rectangles: supply some vertices via mouse input, create and update meshes/colliders, and simulate finished shapes. The DrawController
’s code would largely be unchanged: what we need are scripts defined for each shape on how their mesh and collider should be drawn.
But there’s a small catch: how do we keep track of all the different types of shapes? I opted to use inheritance as it is a tidy way of getting a handle on all shapes and update them by their public interface. First, we create an abstract class inheriting from Monobehaviour
called DrawShape.cs
, which looks like this:
As we want to manipulate the GameObject
properties, we use an abstract class instead of an interface. There may be some code duplication between shapes, but this format enables shapes to be independent of each other in terms of implementation.
The DrawRectangle.cs
script should then be updated to inherit from DrawShape
, with some additional override
keywords for the appropriate methods/properties.
As we will be drawing rectangles, circles, and triangles, we should update DrawController.cs
with all prefabs that we will be using and expose them as public members for Unity, on for each shape type. To make shape selection easier, we will create an enum called DrawMode
that maps a shape type to a prefab so that the requested shape type will be instantiated upon drawing a new shape. There will also be a handy dropdown in the Unity inspector to change between the shape we want to draw.
Drawing Circles
Unity does not support creating circle meshes or drawing circles out of the box, so we will need to implement our own circle-drawing algorithm. Fortunately, it does not require a large amount of code. The basic idea is to approximate a circle by drawing a regular polygon with enough line segments to be indistinguishable from a circle. To keep this illusion, the number of segments need to increase as the circle’s perimeter increases.
Below is the script for DrawCircle.cs
, which inherits from DrawShape
. Most of the code is identical to DrawRectangle.cs
. Here we use a CircleCollider2D
instead of a BoxCollider2D
. The biggest difference is in the CircleMesh(Vector2 v0, Vector2 v1, Color fillColor)
method.
We can create a manually tuned a linear function called numSegments
which increases proportionally to the radius. For each segment, we evenly space Vector2
points around the circle using cosine and sine functions. Then the rest is just a repeat of what we have seen before in DrawRectangle
.
Check out commit 4 - draw circle and add physics to view the results in Unity.
Drawing Triangles
With triangles, just change the collider type to PolygonCollider2D
and we are halfway there. We can create a triangle mesh directly from the list of vertices specified by the cursor.
Can you guess how to draw arbitrary concave polygons? Hint: redefine the condition for when the shape is finished. Check out commit 5 - draw triangle and add physics to view the results in Unity.
Conclusion
Congratulations on making it this far! As a reward, here’s an easter egg: press the spacebar when your cursor is near some shapes.
I hope you enjoyed the tutorial and have a newfound appreciation for Unity’s simple physics interface. And if anyone wants to collaborate with me on creating a full featured game out of this, contact me.