Generating BoxColliders for PicaVoxel-imported objects in Unity3D

Published September 22, 2016
Advertisement

Recently I downloaded MagicaVoxel which lets you create and modify blocky objects, and bought PicaVoxel which lets you import them into Unity3D projects. The purpose was twofold: to help my friend Highsight develop his first game, and to have access to powerful tools in case I ever wanted to make a voxel art game.

Highsight claimed difficulties with Rigidbody objects flying through the imported voxel objects. Upon looking at how the MeshColliders were built for the voxel objects, I guessed that the issue was with the fact the colliders were paper thin. Seeing that all of the voxel faces were one-sided, I figured that the best way to handle the issue was to create a BoxCollider behind every face.

The script below, when attached to a voxel object, will iterate through all Chunk objects and generate bounding boxes for them on simulation startup:


(Download the script from http://gamieon.com/blogimages/devblog/20160918/PicaBoxColliderGenerator.cs or copy from below)

using UnityEngine;using System.Collections;using PicaVoxel;/// /// Attach this script to a voxel object to generate box colliders for all the chunks therein/// when the game or simulation begins. The advantage of this over using existing mesh colliders/// is that these colliders have depth and are therefore less prone to objects going through or/// other weird collision behavior happening/// public class PicaBoxColliderGenerator : MonoBehaviour { /// /// The thickness of a box collider. Would be nice to auto-calculate this someday /// public float thickness = 0.1f; /// /// True if the box colliders should be static /// public bool staticColliders = false; // Use this for initialization void Start () { GenerateBoxColliders(); } /// /// Generates box colliders for all child chunks of this object /// void GenerateBoxColliders() { foreach (Chunk c in gameObject.GetComponentsInChildren()) { GenerateBoxColliders(c); } } /// /// Generates box colliders for a single child chunk of this object /// /// The chunk void GenerateBoxColliders(Chunk chunk) { // MAJOR ASSUMPTION: The object is not static (or else mesh optimization occurs and you'll get an error getting the mesh) // First get the mesh information MeshFilter meshFilter = chunk.gameObject.GetComponent(); Mesh mesh = meshFilter.sharedMesh; // MAJOR ASSUMPTION: Each pair of triangles is a rectangle // Now do for all rectangles for (int i = 0; i < mesh.triangles.Length; i += 6) { // Get the vertices of one of the triangles that make this rectangle // // v2 ------- v1 // | | // | | // v3 ------- * // Vector3 v1 = mesh.vertices[mesh.triangles]; Vector3 v2 = mesh.vertices[mesh.triangles[i + 1]]; Vector3 v3 = mesh.vertices[mesh.triangles[i + 2]]; // Get the two sides of the triangle which also make up two edges of the rectangle // connected by the same vertex (v2) Vector3 a = v1 - v2; Vector3 b = v3 - v2; // Get the normalized vector of the rectangle facing inward Vector3 n = Vector3.Cross(a.normalized, b.normalized); // Get the center of the rectangle by calculating the midpoint of two diagonally opposing points Vector3 c = (v1 + v3) * 0.5f; // Create an empty object at the center of the rectangle and make it a child of the chunk GameObject go = new GameObject("ChunkBoxCollider"); go.transform.SetParent(chunk.transform); // Position the object such that when we eventually add the collider, the "outermost" face of the collider // will be in the same plane as the rectangle go.transform.localPosition = c + n * thickness * 0.5f; // The object should always be facing the rectangle. This is so when we size the box collider, // we can be sure of the role that each axe has. Keep in mind that LookAt deals in world coordinates // so we need to adjust for the chunk's world rotation go.transform.LookAt(go.transform.position + chunk.transform.rotation * n, chunk.transform.rotation * b.normalized); // Now create the box collider BoxCollider boxCollider = go.AddComponent(); // Size the box collider boxCollider.size = new Vector3(a.magnitude, b.magnitude, thickness); // Make the collider static if desired go.isStatic = staticColliders; } }}

The script has two properties


  • thickness: How thick the colliders should be (0.4 works well)
  • staticColliders: If true, all the colliders will be static

Keep in mind that if your voxel object is static, the script will have problems accessing the mesh data. You can address this by making the object not static.

Because of the time it takes to generate the BoxColliders, you may want to consider writing an Editor script based on PicaBoxColliderGenerator to generate those boxes at design time instead of run time.

Here is how the scene looked with no colliders, and how it looked with box colliders.

scene1.jpg

scene2.jpg


On testing it with box colliders, I had no issues with Rigidbodies flying through the voxel at reasonable speeds. Highsight found the same to be true as well.

I hope you find this helpful! Even if you don't, please check out my portfolio and follow me!

Games: http://www.gamieon.com/games
Twitch: https://www.twitch.tv/gamieon
Facebook: https://www.facebook.com/gamieon
Twitter: https://www.twitter.com/gamieon
YouTube: https://www.youtube.com/gamieon

2 likes 1 comments

Comments

Navyman

Collision is some an area that has become a staple for a vast majority of games and few people have a good grip on the topic.

Thank you for sharing your knowledge and brighten/enlighten others.

January 18, 2017 05:46 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement