如何在Unity3D C#中实现支持Skinned Mesh Renderer的无碰撞器自定义射线检测
Hey there! Let's tackle your problem step by step. First, let's address your questions about Unity's RaycastHit source code, then dive into building that custom raycast system for your FPS.
Can I Access Unity's RaycastHit Source Code?
Unfortunately, Unity's core physics engine (including the internal implementation of RaycastHit and built-in raycast logic) is closed-source. You won't be able to view the raw source code for these components. That said, Unity's official documentation thoroughly explains all properties and usage of RaycastHit—which is all you need to replicate its functionality for your shooting detection use case.
Building a Custom Raycast System for Skinned Mesh Renderer
The key here is to work directly with the animated vertex data of your Skinned Mesh Renderer, instead of relying on collision components. Here's a structured, performance-friendly approach:
1. Bake the Skinned Mesh in Real-Time
Skinned meshes update vertex positions dynamically as animations play. To get the current, animated state of the mesh, use SkinnedMeshRenderer.BakeMesh() to render the current pose into a temporary Mesh object. This gives you up-to-date vertices, triangles, and normals for raycasting.
// In your script, declare these variables [SerializeField] private SkinnedMeshRenderer _playerSkinnedRenderer; private Mesh _tempBakedMesh; void Awake() { _tempBakedMesh = new Mesh(); } // Call this when you need to perform a raycast (e.g., on player shoot) void PerformShootRaycast(Ray shootRay) { // Bake the current animated mesh into the temp mesh _playerSkinnedRenderer.BakeMesh(_tempBakedMesh); // Now use _tempBakedMesh for ray-triangle intersection checks }
2. Ray-Triangle Intersection Logic
The core of your custom raycast will be checking if your shoot ray intersects any triangle in the baked mesh. The Möller–Trumbore algorithm is the industry standard for fast, accurate ray-triangle intersection. Here's a simplified C# implementation tailored for Unity:
// Helper struct to replicate RaycastHit functionality (customize as needed) public struct CustomHitInfo { public Vector3 point; public float distance; public Vector3 normal; // Add extra properties like triangle index or bone ID if needed } // Check if a ray intersects a single triangle private bool RayIntersectsTriangle(Ray ray, Vector3 v0, Vector3 v1, Vector3 v2, out CustomHitInfo hitInfo) { hitInfo = new CustomHitInfo(); Vector3 edge1 = v1 - v0; Vector3 edge2 = v2 - v0; Vector3 h = Vector3.Cross(ray.direction, edge2); float a = Vector3.Dot(edge1, h); // Ray is parallel to triangle if (a > -Mathf.Epsilon && a < Mathf.Epsilon) return false; float f = 1f / a; Vector3 s = ray.origin - v0; float u = f * Vector3.Dot(s, h); if (u < 0f || u > 1f) return false; Vector3 q = Vector3.Cross(s, edge1); float v = f * Vector3.Dot(ray.direction, q); if (v < 0f || u + v > 1f) return false; // Calculate intersection distance float t = f * Vector3.Dot(edge2, q); if (t > Mathf.Epsilon) { hitInfo.point = ray.origin + ray.direction * t; hitInfo.distance = t; hitInfo.normal = Vector3.Cross(edge1, edge2).normalized; return true; } return false; }
3. Optimize the Detection
Traversing every triangle can be slow for high-poly models. Add these optimizations to keep performance snappy:
- Bounds Check First: Before checking every triangle, test if the ray intersects the baked mesh's
Bounds. If not, skip all triangle checks entirely:if (!_tempBakedMesh.bounds.IntersectRay(shootRay)) return; - Only Run on Shooting: Don't perform this check every frame. Trigger it only when the player fires a shot—this drastically reduces calculation frequency.
- Use a Simplified Mesh: If your player model is extremely high-poly, bake a low-poly skinned mesh specifically for raycasting (it'll be faster and still accurate enough for hit detection).
4. Assemble the Full Raycast Function
Put it all together to find the closest intersection point (just like Unity's built-in Physics.Raycast):
public bool CustomSkinnedMeshRaycast(Ray shootRay, out CustomHitInfo closestHit) { closestHit = new CustomHitInfo(); closestHit.distance = Mathf.Infinity; // Bake the current mesh pose _playerSkinnedRenderer.BakeMesh(_tempBakedMesh); // Early exit if ray doesn't hit the mesh bounds if (!_tempBakedMesh.bounds.IntersectRay(shootRay)) return false; // Get mesh data int[] triangles = _tempBakedMesh.triangles; Vector3[] vertices = _tempBakedMesh.vertices; Transform meshTransform = _playerSkinnedRenderer.transform; bool hitFound = false; // Iterate through each triangle for (int i = 0; i < triangles.Length; i += 3) { // Convert local vertex positions to world space Vector3 v0 = meshTransform.TransformPoint(vertices[triangles[i]]); Vector3 v1 = meshTransform.TransformPoint(vertices[triangles[i+1]]); Vector3 v2 = meshTransform.TransformPoint(vertices[triangles[i+2]]); CustomHitInfo tempHit; if (RayIntersectsTriangle(shootRay, v0, v1, v2, out tempHit)) { hitFound = true; // Keep track of the closest hit if (tempHit.distance < closestHit.distance) { closestHit = tempHit; } } } return hitFound; }
Usage Example
Call this function when the player shoots:
void Update() { if (Input.GetMouseButtonDown(0)) { Ray shootRay = Camera.main.ScreenPointToRay(Input.mousePosition); CustomHitInfo hit; if (CustomSkinnedMeshRaycast(shootRay, out hit)) { Debug.Log($"Hit player at {hit.point}, distance: {hit.distance}"); // Handle damage, hit effects, etc. } } }
Final Notes
This approach avoids the performance overhead of Mesh Collider (which recalculates collision data every frame for animated meshes) by only computing intersections when needed. Since you're working directly with the mesh's vertex data, it's fully accurate to the animated pose of your player model.
内容的提问来源于stack exchange,提问作者Je moder

