This commit is contained in:
2025-10-15 20:13:48 +08:00
parent 0fd74a1a45
commit 1b17489c11
27 changed files with 3196 additions and 302 deletions

View File

@@ -0,0 +1,259 @@
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class MarchingSquaresProcessor
{
[Header("Marching Squares设置")]
public float isoLevel = 0.5f;
public bool smoothEdges = true;
public float smoothness = 0.1f;
// Marching Squares查找表
private static readonly int[,] transitionTable = new int[,]
{
{-1, -1, -1, -1}, // 0: 空单元格
{ 0, 3, -1, -1}, // 1: 只有左下角
{ 1, 0, -1, -1}, // 2: 只有右下角
{ 1, 3, -1, -1}, // 3: 下边
{ 2, 1, -1, -1}, // 4: 只有右上角
{ 0, 3, 2, 1}, // 5: S形状
{ 2, 0, -1, -1}, // 6: 右边
{ 2, 3, -1, -1}, // 7: 右下三角
{ 3, 2, -1, -1}, // 8: 只有左上角
{ 0, 2, -1, -1}, // 9: 左边
{ 0, 1, 3, 2}, // 10: S形状反转
{ 1, 2, -1, -1}, // 11: 左下三角
{ 3, 1, -1, -1}, // 12: 上边
{ 3, 0, -1, -1}, // 13: 右上三角
{ 1, 3, -1, -1}, // 14: 左上三角
{-1, -1, -1, -1} // 15: 全满
};
public List<List<Vector2>> ExtractContours(DensityGrid grid)
{
var contours = new List<List<Vector2>>();
bool[,] visited = new bool[grid.Width, grid.Height];
for (int x = 0; x < grid.Width - 1; x++)
{
for (int y = 0; y < grid.Height - 1; y++)
{
if (!visited[x, y] && IsBoundaryCell(grid, x, y))
{
var contour = MarchCell(grid, x, y, visited);
if (contour != null && contour.Count >= 3)
{
if (smoothEdges)
contour = SmoothContour(contour);
contours.Add(contour);
}
}
}
}
return contours;
}
List<Vector2> MarchCell(DensityGrid grid, int startX, int startY, bool[,] visited)
{
var contour = new List<Vector2>();
int x = startX, y = startY;
int step = 0;
int maxSteps = grid.Width * grid.Height; // 防止无限循环
do
{
// 边界检查
if (x < 0 || x >= grid.Width - 1 || y < 0 || y >= grid.Height - 1)
break;
if (visited[x, y])
break;
visited[x, y] = true;
int config = GetCellConfiguration(grid, x, y);
var edgePoints = GetEdgePoints(grid, x, y, config);
// 只添加有效的点
foreach (var point in edgePoints)
{
if (!contour.Contains(point)) // 简单的去重
contour.Add(point);
}
// 移动到下一个单元格
if (!MoveToNextCell(config, ref x, ref y))
break;
step++;
} while ((x != startX || y != startY) && step < maxSteps);
return contour;
}
int GetCellConfiguration(DensityGrid grid, int x, int y)
{
int config = 0;
// 安全地获取值,处理边界情况
float a = GetSafeValue(grid, x, y);
float b = GetSafeValue(grid, x + 1, y);
float c = GetSafeValue(grid, x + 1, y + 1);
float d = GetSafeValue(grid, x, y + 1);
if (a >= isoLevel) config |= 1;
if (b >= isoLevel) config |= 2;
if (c >= isoLevel) config |= 4;
if (d >= isoLevel) config |= 8;
return config;
}
float GetSafeValue(DensityGrid grid, int x, int y)
{
if (x < 0 || x >= grid.Width || y < 0 || y >= grid.Height)
return 0f;
return grid.GetValue(x, y);
}
Vector2[] GetEdgePoints(DensityGrid grid, int x, int y, int config)
{
var points = new List<Vector2>();
// 根据配置添加边点
switch (config)
{
case 1:
case 14:
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
break;
case 2:
case 13:
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
break;
case 3:
case 12:
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
break;
case 4:
case 11:
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
break;
case 5:
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
break;
case 6:
case 9:
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
break;
case 7:
case 8:
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
break;
case 10:
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
break;
}
return points.ToArray();
}
Vector2 InterpolateEdge(DensityGrid grid, int x1, int y1, int x2, int y2)
{
// 安全获取值
float v1 = GetSafeValue(grid, x1, y1);
float v2 = GetSafeValue(grid, x2, y2);
if (Mathf.Abs(v1 - v2) < 0.001f)
return grid.GridToLocalPosition(x1, y1);
float t = (isoLevel - v1) / (v2 - v1);
t = Mathf.Clamp01(t);
Vector2 p1 = grid.GridToLocalPosition(x1, y1);
Vector2 p2 = grid.GridToLocalPosition(x2, y2);
return Vector2.Lerp(p1, p2, t);
}
bool MoveToNextCell(int config, ref int x, ref int y)
{
// 简化的移动逻辑 - 根据配置决定下一个单元格
switch (config)
{
case 1:
case 3:
case 7:
case 5:
y++; break; // 向上移动
case 2:
case 6:
case 10:
case 14:
x++; break; // 向右移动
case 4:
case 12:
case 13:
case 15:
y--; break; // 向下移动
case 8:
case 9:
case 11:
x--; break; // 向左移动
default:
return false; // 无法移动
}
return true;
}
List<Vector2> SmoothContour(List<Vector2> contour)
{
if (contour.Count < 4) return contour;
var smoothed = new List<Vector2>();
for (int i = 0; i < contour.Count; i++)
{
Vector2 prev = contour[(i - 1 + contour.Count) % contour.Count];
Vector2 current = contour[i];
Vector2 next = contour[(i + 1) % contour.Count];
Vector2 smoothedPoint = (prev + current * 2f + next) / 4f;
smoothed.Add(smoothedPoint);
}
return smoothed;
}
bool IsBoundaryCell(DensityGrid grid, int x, int y)
{
// 安全检查
if (x < 0 || x >= grid.Width - 1 || y < 0 || y >= grid.Height - 1)
return false;
float value = grid.GetValue(x, y);
return value >= isoLevel && value < 1.0f; // 边界单元格
}
public void DrawGridGizmos(Transform wall)
{
// 调试可视化代码可以留空或简化
#if UNITY_EDITOR
// 可选:添加网格可视化
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2a4c434153f3614d8381195368e2fec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using UnityEngine;
using System.Collections.Generic;
public class ShadowCollider : MonoBehaviour
{
[Header("阴影碰撞器")]
public GameObject sourceObject;
public PolygonCollider2D polygonCollider;
[Header("调试")]
public bool showGizmos = true;
public Color gizmoColor = new Color(1, 0, 0, 0.3f);
public void Initialize(List<List<Vector2>> contours)
{
polygonCollider = GetComponent<PolygonCollider2D>();
if (polygonCollider == null)
polygonCollider = gameObject.AddComponent<PolygonCollider2D>();
UpdateCollider(contours);
}
public void UpdateCollider(List<List<Vector2>> contours)
{
if (polygonCollider == null) return;
polygonCollider.pathCount = contours.Count;
for (int i = 0; i < contours.Count; i++)
{
polygonCollider.SetPath(i, contours[i].ToArray());
}
}
#if UNITY_EDITOR
void OnDrawGizmos()
{
if (!showGizmos || polygonCollider == null) return;
Gizmos.color = gizmoColor;
Vector3 offset = transform.forward * 0.01f;
for (int pathIndex = 0; pathIndex < polygonCollider.pathCount; pathIndex++)
{
var path = polygonCollider.GetPath(pathIndex);
for (int i = 0; i < path.Length; i++)
{
Vector3 point1 = transform.TransformPoint(path[i]);
Vector3 point2 = transform.TransformPoint(path[(i + 1) % path.Length]);
Gizmos.DrawLine(point1 + offset, point2 + offset);
}
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a96ca5e4a935984392a295d8855f0f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,183 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Rendering.Universal;
public class ShadowColliderSystem : MonoBehaviour
{
[Header("系统设置")]
public Light shadowLight;
public Transform shadowWall;
public string shadowLayerName = "Shadow";
public LayerMask obstacleLayerMask = -1;
[Header("生成设置")]
public bool generateOnStart = true;
public bool updateInRealTime = false;
public float updateInterval = 0.1f;
[Header("阴影碰撞器")]
public List<ShadowCollider> shadowColliders = new List<ShadowCollider>();
private ShadowProjector projector;
private ShadowMeshGenerator meshGenerator;
private MarchingSquaresProcessor marchingSquares;
private float updateTimer;
void Start()
{
InitializeComponents();
if (generateOnStart)
{
GenerateAllShadowColliders();
}
}
void Update()
{
if (updateInRealTime)
{
updateTimer += Time.deltaTime;
if (updateTimer >= updateInterval)
{
UpdateAllShadowColliders();
updateTimer = 0f;
}
}
}
void InitializeComponents()
{
// 自动查找必要的组件
if (shadowLight == null) shadowLight = ShadowUtils.FindMainLight();
if (shadowWall == null) shadowWall = ShadowUtils.FindMainWall();
// 初始化子系统
projector = new ShadowProjector(shadowLight, shadowWall, obstacleLayerMask);
meshGenerator = new ShadowMeshGenerator();
marchingSquares = new MarchingSquaresProcessor();
}
[ContextMenu("生成所有阴影碰撞器")]
public void GenerateAllShadowColliders()
{
ClearAllShadowColliders();
var shadowObjects = ShadowUtils.FindObjectsInLayer(shadowLayerName);
foreach (GameObject obj in shadowObjects)
{
GenerateShadowCollider(obj);
}
Debug.Log($"生成了 {shadowColliders.Count} 个阴影碰撞器");
}
[ContextMenu("更新所有阴影碰撞器")]
public void UpdateAllShadowColliders()
{
foreach (var shadowCollider in shadowColliders)
{
if (shadowCollider != null)
{
UpdateShadowCollider(shadowCollider);
}
}
}
public void GenerateShadowCollider(GameObject shadowCaster)
{
if (!IsValidShadowCaster(shadowCaster)) return;
try
{
// 1. 投影网格到墙面
var projectedMesh = projector.ProjectMeshToWall(shadowCaster);
if (!projectedMesh.IsValid)
{
Debug.LogWarning($"无法为 {shadowCaster.name} 生成有效投影网格");
return;
}
// 2. 生成阴影密度网格
var densityGrid = meshGenerator.GenerateDensityGrid(projectedMesh, shadowWall);
// 3. 使用Marching Squares提取轮廓
var contours = marchingSquares.ExtractContours(densityGrid);
if (contours == null || contours.Count == 0)
{
Debug.LogWarning($"无法为 {shadowCaster.name} 提取轮廓");
return;
}
// 4. 创建阴影碰撞器对象
var shadowCollider = CreateShadowColliderObject(shadowCaster.name, contours);
shadowCollider.sourceObject = shadowCaster;
shadowColliders.Add(shadowCollider);
Debug.Log($"成功为 {shadowCaster.name} 生成阴影碰撞器,包含 {contours.Count} 个轮廓");
}
catch (System.Exception e)
{
Debug.LogError($"为 {shadowCaster.name} 生成阴影碰撞器时出错: {e.Message}");
}
}
public void UpdateShadowCollider(ShadowCollider shadowCollider)
{
if (shadowCollider.sourceObject == null) return;
var projectedMesh = projector.ProjectMeshToWall(shadowCollider.sourceObject);
var densityGrid = meshGenerator.GenerateDensityGrid(projectedMesh, shadowWall);
var contours = marchingSquares.ExtractContours(densityGrid);
shadowCollider.UpdateCollider(contours);
}
[ContextMenu("清空所有阴影碰撞器")]
public void ClearAllShadowColliders()
{
foreach (var collider in shadowColliders)
{
if (collider != null && collider.gameObject != null)
{
DestroyImmediate(collider.gameObject);
}
}
shadowColliders.Clear();
}
bool IsValidShadowCaster(GameObject obj)
{
return obj != null &&
obj.activeInHierarchy &&
obj.GetComponent<MeshFilter>() != null;
}
ShadowCollider CreateShadowColliderObject(string name, List<List<Vector2>> contours)
{
GameObject colliderObj = new GameObject($"{name}_ShadowCollider");
colliderObj.transform.SetParent(shadowWall);
colliderObj.transform.localPosition = Vector3.zero;
colliderObj.transform.localRotation = Quaternion.identity;
var shadowCollider = colliderObj.AddComponent<ShadowCollider>();
shadowCollider.Initialize(contours);
return shadowCollider;
}
void OnDestroy()
{
ClearAllShadowColliders();
}
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
if (marchingSquares != null && shadowWall != null)
{
marchingSquares.DrawGridGizmos(shadowWall);
}
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 836b4625937ba9d45be59618cd2a6a15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class ShadowMeshGenerator
{
[Header("网格设置")]
public int gridResolution = 64;
public float gridSize = 1.0f;
public float influenceRadius = 0.5f;
public DensityGrid GenerateDensityGrid(ProjectedMesh mesh, Transform wall)
{
var bounds = CalculateMeshBounds(mesh);
var grid = new DensityGrid(bounds, gridResolution, gridSize);
// 为每个网格点计算密度
for (int x = 0; x < grid.Width; x++)
{
for (int y = 0; y < grid.Height; y++)
{
Vector2 gridPos = grid.GridToLocalPosition(x, y);
float density = CalculateDensityAtPoint(gridPos, mesh);
grid.SetValue(x, y, density);
}
}
return grid;
}
float CalculateDensityAtPoint(Vector2 point, ProjectedMesh mesh)
{
float totalDensity = 0f;
int samples = 0;
// 采样网格点周围的顶点影响
foreach (var vertex in mesh.vertices)
{
Vector2 vertex2D = new Vector2(vertex.x, vertex.y);
float distance = Vector2.Distance(point, vertex2D);
if (distance < influenceRadius)
{
float influence = 1f - (distance / influenceRadius);
totalDensity += influence;
samples++;
}
}
return samples > 0 ? totalDensity / samples : 0f;
}
Bounds CalculateMeshBounds(ProjectedMesh mesh)
{
if (mesh.vertices.Count == 0)
return new Bounds(Vector3.zero, Vector3.one);
Vector3 min = mesh.vertices[0];
Vector3 max = mesh.vertices[0];
foreach (var vertex in mesh.vertices)
{
min = Vector3.Min(min, vertex);
max = Vector3.Max(max, vertex);
}
return new Bounds((min + max) * 0.5f, max - min);
}
}
[System.Serializable]
public class DensityGrid
{
public int Width { get; private set; }
public int Height { get; private set; }
public float CellSize { get; private set; }
public Bounds WorldBounds { get; private set; }
private float[,] values;
private Vector3 gridOrigin;
public DensityGrid(Bounds bounds, int resolution, float cellSize)
{
WorldBounds = bounds;
CellSize = cellSize;
Width = resolution;
Height = resolution;
gridOrigin = bounds.min;
values = new float[Width, Height];
}
public void SetValue(int x, int y, float value)
{
if (x >= 0 && x < Width && y >= 0 && y < Height)
{
values[x, y] = Mathf.Clamp01(value);
}
}
public float GetValue(int x, int y)
{
if (x >= 0 && x < Width && y >= 0 && y < Height)
{
return values[x, y];
}
return 0f;
}
public Vector2 GridToLocalPosition(int x, int y)
{
return new Vector2(
x * CellSize + gridOrigin.x,
y * CellSize + gridOrigin.y
);
}
public Vector3 GridToWorldPosition(int x, int y, Transform wall)
{
Vector2 localPos = GridToLocalPosition(x, y);
return wall.TransformPoint(new Vector3(localPos.x, localPos.y, 0));
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 85170fe0e92d3604fbf662a3620570e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class ShadowProjector
{
public Light lightSource;
public Transform projectionWall;
public LayerMask obstacleMask;
private Vector3 wallNormal;
private Vector3 wallPosition;
public ShadowProjector(Light light, Transform wall, LayerMask obstacleMask)
{
this.lightSource = light;
this.projectionWall = wall;
this.obstacleMask = obstacleMask;
CalculateWallPlane();
}
public ProjectedMesh ProjectMeshToWall(GameObject shadowCaster)
{
var meshFilter = shadowCaster.GetComponent<MeshFilter>();
if (meshFilter == null) return new ProjectedMesh();
var mesh = meshFilter.mesh;
var vertices = mesh.vertices;
var triangles = mesh.triangles;
List<Vector3> projectedVertices = new List<Vector3>();
List<int> projectedTriangles = new List<int>();
// 投影每个顶点
for (int i = 0; i < vertices.Length; i++)
{
Vector3 worldVertex = shadowCaster.transform.TransformPoint(vertices[i]);
Vector3 projectedVertex = ProjectPoint(worldVertex);
if (!IsObstructed(worldVertex, projectedVertex))
{
projectedVertices.Add(projectedVertex);
}
}
// 重新构建三角形(简化版本)
// 实际应该使用更复杂的三角化算法
for (int i = 0; i < triangles.Length; i += 3)
{
if (IsValidTriangle(projectedVertices, triangles, i))
{
projectedTriangles.Add(triangles[i]);
projectedTriangles.Add(triangles[i + 1]);
projectedTriangles.Add(triangles[i + 2]);
}
}
return new ProjectedMesh
{
vertices = projectedVertices,
triangles = projectedTriangles.ToArray(),
sourceObject = shadowCaster
};
}
Vector3 ProjectPoint(Vector3 worldPoint)
{
Vector3 lightDirection = GetLightDirection(worldPoint);
return IntersectWithWall(worldPoint, lightDirection);
}
Vector3 GetLightDirection(Vector3 targetPoint)
{
if (lightSource.type == LightType.Directional)
{
return -lightSource.transform.forward;
}
else
{
return (targetPoint - lightSource.transform.position).normalized;
}
}
Vector3 IntersectWithWall(Vector3 point, Vector3 direction)
{
float denominator = Vector3.Dot(direction, wallNormal);
if (Mathf.Abs(denominator) < 0.0001f)
return point;
float t = Vector3.Dot(wallPosition - point, wallNormal) / denominator;
return point + direction * t;
}
bool IsObstructed(Vector3 start, Vector3 end)
{
Vector3 direction = (end - start).normalized;
float distance = Vector3.Distance(start, end);
RaycastHit hit;
if (Physics.Raycast(start, direction, out hit, distance, obstacleMask))
{
return hit.collider.gameObject != projectionWall.gameObject;
}
return false;
}
bool IsValidTriangle(List<Vector3> vertices, int[] triangles, int startIndex)
{
int i1 = triangles[startIndex];
int i2 = triangles[startIndex + 1];
int i3 = triangles[startIndex + 2];
return i1 < vertices.Count && i2 < vertices.Count && i3 < vertices.Count;
}
void CalculateWallPlane()
{
if (projectionWall != null)
{
wallPosition = projectionWall.position;
wallNormal = -projectionWall.forward;
}
}
}
[System.Serializable]
public struct ProjectedMesh
{
public List<Vector3> vertices;
public int[] triangles;
public GameObject sourceObject;
public bool IsValid => vertices != null && vertices.Count >= 3;
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 462c6c18ec968ae4badc890a169194e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public static class ShadowUtils
{
public static Light FindMainLight()
{
Light[] lights = Object.FindObjectsOfType<Light>();
return lights.FirstOrDefault(l => l.type == LightType.Directional && l.enabled) ??
lights.FirstOrDefault(l => l.type == LightType.Spot && l.enabled);
}
public static Transform FindMainWall()
{
var walls = Object.FindObjectsOfType<Collider>()
.Where(c => c is BoxCollider)
.OrderByDescending(c =>
{
var box = c as BoxCollider;
var size = Vector3.Scale(box.size, c.transform.lossyScale);
return size.x * size.y;
});
return walls.FirstOrDefault()?.transform;
}
public static List<GameObject> FindObjectsInLayer(string layerName)
{
int layer = LayerMask.NameToLayer(layerName);
return Object.FindObjectsOfType<GameObject>()
.Where(obj => obj.activeInHierarchy && obj.layer == layer)
.ToList();
}
public static List<GameObject> FindObjectsInLayer(int layer)
{
return Object.FindObjectsOfType<GameObject>()
.Where(obj => obj.activeInHierarchy && obj.layer == layer)
.ToList();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5f9eb8ac9da6a8488b456411e1e7370
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: