- Home /
Unity 5 non kinematic convex mesh collider requirement breaks ability to check materials with casting, is there a workaround?
I will paste code for a script to test this below but basically raycasting or spherecasting in Unity 4 and checking the material of the triangle that was hit worked perfectly because we were checking the hit triangle on the collider against the triangles in the mesh filter. In Unity 5 it fails because both types of casting always hit the generated convex collider triangles which don't match the mesh filter triangles. The end result is that if you have a non kinematic RB, you are forced to enable the Convex check box which disables the ability to check materials.
That ability is critical in my game. All platforms that move or rotate are controlled with forces as is the player (which keeps the player on platforms instead of them sliding out from under his feet) and the player responds to different types of materials within a mesh filter. Example, if the material is Crawlable, the character can climb it, if the material is Ice, the player slides, Orient causes the player to orient to the surface normal. Without the ability to check the material, my game is broken. For example, this gear, the player would orient to the entire surface but only be able to climb and stick to the green parts because they have a crawlable material.
Below is the code I am using:
Attach a script with the code to an empty and place it over a surface with a mesh collider and a material that has the word Crawlable in the name.
Play your scene.
Pressing Z does a Raycast, X a Spherecast. Both will return True, check the debug output after pressing Z and X
Stop playing
Attach a non kinematic RB to the surface with the collider, you are required to now enable convex, so enable convex
Play your scene
Press Z or X, they will both reward you with errors because they are hitting the auto generated convex collider which does not match your mesh filter. . .
Is there a way to get the material name of the triangle the player is above on a convex mesh collider in Unity 5? or some better way to do this? I am all ears and pretty stuck without a solution.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Assets._Code.Test
{
public class RaycastMatCheckTest : MonoBehaviour
{
private RaycastHit _hitDownward;
private Ray _rayDownward;
private int[] _hitTriangle;
private int[][] _subMeshTrisJagged;
private int[] _mTriangles;
private Material[] _materials;
private int[][] _subMeshTrisJaggedLvl;
private int[] _mTrianglesLvl;
private Material[] _materialsLvl;
private Mesh m;
private Mesh mLvl;
private Mesh _mPrevious;
private MeshFilter mf;
private MeshFilter mfLvl;
private Dictionary<string, bool>[] _materialsLvlKeyVal;
private Dictionary<string, bool>[] _materialsKeyVal;
private String[] _materialKeys;
void Start()
{
_hitTriangle = new int[3];
_materialKeys = new[]
{
"Crawlable",
"Orient",
"Parent",
"Ice"
};
//Setup Lvl arrays to keep for default so they don't get GCd
mfLvl = GameObject.FindGameObjectWithTag("Ground").GetComponent<MeshFilter>();
mLvl = mfLvl.sharedMesh;
_mTrianglesLvl = mLvl.triangles;
_subMeshTrisJaggedLvl = new int[mLvl.subMeshCount][];
_materialsLvl = mfLvl.transform.gameObject.GetComponent<Renderer>().materials;
for (int i = 0; i < _subMeshTrisJaggedLvl.Length; i++)
{
_subMeshTrisJaggedLvl[i] = mLvl.GetTriangles(i);
}
_materialsLvlKeyVal = new Dictionary<string, bool>[_materialsLvl.Length];
for (int j = 0; j < _materialsLvlKeyVal.Length; j++)
{
_materialsLvlKeyVal[j] = new Dictionary<string, bool>();
for (int k = 0; k < _materialKeys.Length; k++)
{
_materialsLvlKeyVal[j].Add(_materialKeys[k], _materialsLvl[j].name.Contains(_materialKeys[k]));
}
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
Physics.Raycast(transform.position, Vector3.down, out _hitDownward);
Debug.Log("Material is Crawlable " + CheckMaterial(_hitDownward, "Crawlable"));
}
if (Input.GetKeyDown(KeyCode.X))
{
Physics.SphereCast(transform.position, 0.25f,Vector3.down, out _hitDownward);
Debug.Log("Material is Crawlable " + CheckMaterial(_hitDownward, "Crawlable"));
}
}
bool CheckMaterial(RaycastHit hit, string stringName)
{
if (hit.collider == null || hit.collider.GetType() != typeof(MeshCollider) ||
!hit.transform.gameObject.GetComponent<Renderer>())
{
return false;
}
m = GetMesh(hit.transform.gameObject);
if (m == mLvl)
{
_hitTriangle[0] = _mTrianglesLvl[hit.triangleIndex * 3];
_hitTriangle[1] = _mTrianglesLvl[hit.triangleIndex * 3 + 1];
_hitTriangle[2] = _mTrianglesLvl[hit.triangleIndex * 3 + 2];
for (int i = 0; i < _subMeshTrisJaggedLvl.Length; i++)
{
for (int j = 0; j < _subMeshTrisJaggedLvl[i].Length; j += 3)
{
if (_subMeshTrisJaggedLvl[i][j] == _hitTriangle[0] &&
_subMeshTrisJaggedLvl[i][j + 1] == _hitTriangle[1] &&
_subMeshTrisJaggedLvl[i][j + 2] == _hitTriangle[2])
{
return _materialsLvlKeyVal[i][stringName];
}
}
}
}
else if (m)
{
if (m != _mPrevious)
{
_mTriangles = m.triangles;
_subMeshTrisJagged = new int[m.subMeshCount][];
_materials = hit.transform.gameObject.GetComponent<Renderer>().materials;
for (int i = 0; i < _subMeshTrisJagged.Length; i++)
{
_subMeshTrisJagged[i] = m.GetTriangles(i);
}
_materialsKeyVal = new Dictionary<string, bool>[_materials.Length];
for (int j = 0; j < _materialsKeyVal.Length; j++)
{
_materialsKeyVal[j] = new Dictionary<string, bool>();
for (int k = 0; k < _materialKeys.Length; k++)
{
_materialsKeyVal[j].Add(_materialKeys[k], _materials[j].name.Contains(_materialKeys[k]));
}
}
_mPrevious = m;
}
_hitTriangle[0] = _mTriangles[hit.triangleIndex * 3];
_hitTriangle[1] = _mTriangles[hit.triangleIndex * 3 + 1];
_hitTriangle[2] = _mTriangles[hit.triangleIndex * 3 + 2];
for (int i = 0; i < _subMeshTrisJagged.Length; i++)
{
for (int j = 0; j < _subMeshTrisJagged[i].Length; j += 3)
{
if (_subMeshTrisJagged[i][j] == _hitTriangle[0] &&
_subMeshTrisJagged[i][j + 1] == _hitTriangle[1] &&
_subMeshTrisJagged[i][j + 2] == _hitTriangle[2])
{
return _materialsKeyVal[i][stringName];
}
}
}
}
return false;
}
Mesh GetMesh(GameObject go)
{
if (go)
{
mf = go.GetComponent<MeshFilter>();
if (mf)
{
m = mf.sharedMesh;
if (!m) { m = mf.mesh; }
if (m)
{
return m;
}
}
}
return null;
}
}
}
Answer by alebasco · Jun 17, 2015 at 11:11 PM
Its not a solution you're going to like, but I'm pretty sure you're going to have to subdivide the model into models that only have 1 material each. Then you can just get the material of the renderer on the object you hit.
Or instead of making it material based, make it volume based. Add a volume to the area that is climbable, and when the player is in that area, they are climbing.
Thanks, slicing everything up is the only workaround I had thought of because it was how I was doing it before I switched to materials, it had some problems back then in which would only be exacerbated by the same current convex collider restriction causing issues now. In Unity 4, slicing things up had issues because it meant for example, the green part would be its own RB and be embedded inside the other shape with both shapes being dragged along with fixed joints to that center cylinder. When the gear would turn using forces, and also when it stopped, the green part would bounce around inside the other part. That physically couldn't even work right now because the convex requirement would mean I couldn't embed the green part into the other one... as it would just get enveloped inside the other parts convex collider. Which means the only way would be to alter the shape to nothing is embedded in anything, that may work for hit testing but it would hurt gameplay and performance very badly. All crawlable surfaces on or not on platforms are embedded because the interaction makes the most sense from the perspective of the players intentions.
This is also a basic example, things get more complex. For example, there are actually 3 materials on the gear, the main material allows the player to orient to the surface, as does the green one, but at the beveled edges that lead to the green part, the material is not orientable because orienting to that specific part was causing bad gameplay when the player would get on or off the crawlable surface. Anyway, thanks for the potential solution.
I think the biggest issue is that Convex colliders are great for collisions but they should not be forced for raycasting, just like we have the Raycasts Hit Triggers checkbox for Physics, we should have one to make raycasts return the non auto generated collider data when hitting a collider marked as convex or even make it a per collider option. Otherwise this new restriction is amazingly limiting and forces this to be horribly efficient.
Try this: Have a child object with a non-convex mesh collider marked as trigger. Put this object on a different layer, and use that layer for the raycasts while keeping the rigidbody for collisions only.
Interesting idea but not possible with that configuration. If the RB is non kinematic, the collider regardless of placement in the hierarchy must be convex regardless of being a trigger or not. $$anonymous$$ay work if it is a separate object that matches its position and rotation with the RB. As for the layers, the only way I think that may work is if I set the object with the convex collider to Ignore Raycast and hope by ignoring the raycast it doesn't gobble up the raycast and ins$$anonymous$$d allows it to hit the other collider. Will have to test it.
You don't have to set it to ignore raycast, you can have the raycast check only certain layers, and exclude all others. So put the non-convex collider on a layer called "$$anonymous$$aterialCheck" or something and raycast against only that layer.
Additionally, if the collider is non-convex, the RB will throw a warning, but it will still work - it just wont use the non-convex one for collisions - which is what we want. (Also, you could not parent it, and ins$$anonymous$$d have a script forcing it to match rotation and position constantly)
The non-convex collider doesn't actually have to be a trigger either, that was just for ease of use. If you go to Edit->ProjectSettings->Physics you can disable all collisions with that layer, then it will ONLY be used for your custom raycasts, and nothing will have collision events with it.
This solution should actually work for what you're trying to do.
Ah, nice, never used the layer mask functionality with the raycasting. It does sound promising thanks, I won't be able to test it for a few days though. I have gone ahead and marked it as the answer though because I don't envision that failing :) Thank you very much for your help. Won't be able to use my original gear shape but will still have a working gear and other working platforms. Just for reference, the sexy Unity 4 gear is on the left.
Your answer
Follow this Question
Related Questions
Convex NonTrigger Mesh Collider + Rigidbody = weird transform position 0 Answers
remove forward component from velocity vector 2 Answers
Adding Rigidbody to an Object on Collision by Raycast? 1 Answer
0 Understanding of raycast2D commands 2 Answers
Gravity makes player slide on any slope!!! Check if rigidbody Isgrounded not working 0 Answers