第7章 2D游戏开发的数学原理与高级实现
2D游戏开发中的摄像机系统优化 本文介绍了2D游戏开发中的摄像机系统实现,重点分析了正交投影矩阵的数学原理和高级摄像机控制器的设计。通过Unity引擎展示了商业级2D摄像机系统的关键特性,包括: 多分辨率自适应策略 动态缩放功能 目标追踪机制 屏幕震动效果 投影矩阵定制 系统支持三种摄像机模式(正交、透视2D和视差),并提供了像素精度的尺寸计算和边界限制功能。该实现考虑了商业游戏开发中常见的需求,
第7章 2D游戏开发的数学原理与高级实现
7.1 Sprite的数学基础与渲染优化
7.1.1 2D摄像机投影矩阵与坐标变换
在2D游戏开发中,摄像机的作用至关重要。与3D摄像机不同,2D摄像机通常使用正交投影(Orthographic Projection),其投影矩阵的数学原理直接影响着游戏的可视范围和坐标系统。
正交投影矩阵公式:
投影矩阵 = [
[2/(right-left), 0, 0, -(right+left)/(right-left)],
[0, 2/(top-bottom), 0, -(top+bottom)/(top-bottom)],
[0, 0, -2/(far-near), -(far+near)/(far-near)],
[0, 0, 0, 1]
]
在商业级2D游戏中,我们通常需要更复杂的摄像机系统。以下是一个高级2D摄像机控制器的实现:
using UnityEngine;
using System.Collections.Generic;
namespace CommercialProject.Graphics2D
{
/// <summary>
/// 高级2D摄像机控制器
/// 支持多种分辨率自适应策略和动态缩放
/// </summary>
public class AdvancedCamera2D : MonoBehaviour
{
[SerializeField]
private Camera targetCamera;
[SerializeField]
private Vector2 referenceResolution = new Vector2(1920, 1080);
[SerializeField]
private float pixelsPerUnit = 100f;
[SerializeField]
private CameraMode cameraMode = CameraMode.Orthographic;
[SerializeField]
private bool enableDynamicZoom = true;
[SerializeField]
private float minOrthographicSize = 2f;
[SerializeField]
private float maxOrthographicSize = 10f;
// 追踪目标
private List<Transform> trackingTargets;
private Vector2 weightedPosition;
private Vector2 cameraVelocity;
// 边界限制
private Rect cameraBounds;
private bool useBounds = false;
// 屏幕震动
private float shakeIntensity;
private float shakeDuration;
private Vector3 originalPosition;
/// <summary>
/// 摄像机模式
/// </summary>
public enum CameraMode
{
Orthographic,
Perspective2D,
Parallax
}
/// <summary>
/// 摄像机状态
/// </summary>
public struct CameraState
{
public Vector3 Position;
public float OrthographicSize;
public float Rotation;
public Vector4 ViewportRect;
}
private void Awake()
{
InitializeCamera();
trackingTargets = new List<Transform>();
}
private void Start()
{
CalculateOptimalOrthographicSize();
SetupCameraListeners();
}
private void LateUpdate()
{
UpdateCameraPosition();
HandleScreenShake();
ApplyCameraEffects();
}
/// <summary>
/// 初始化摄像机设置
/// </summary>
private void InitializeCamera()
{
if (targetCamera == null)
{
targetCamera = GetComponent<Camera>();
}
// 确保是正交摄像机
targetCamera.orthographic = cameraMode == CameraMode.Orthographic;
// 设置投影矩阵
SetupProjectionMatrix();
// 计算初始位置
originalPosition = transform.position;
}
/// <summary>
/// 设置投影矩阵
/// 基于分辨率和像素精度
/// </summary>
private void SetupProjectionMatrix()
{
// 计算正交投影大小
float orthographicSize = CalculateOrthographicSize();
targetCamera.orthographicSize = orthographicSize;
// 如果需要透视2D效果
if (cameraMode == CameraMode.Perspective2D)
{
SetupPerspective2D();
}
// 应用投影矩阵自定义
ApplyCustomProjection();
}
/// <summary>
/// 计算正交投影大小
/// 基于像素精度和屏幕分辨率
/// </summary>
private float CalculateOrthographicSize()
{
// 基本公式: orthographicSize = (screenHeight / pixelsPerUnit) / 2
float screenHeight = Screen.height;
float baseSize = (screenHeight / pixelsPerUnit) / 2f;
// 考虑参考分辨率
float aspectRatio = (float)Screen.width / Screen.height;
float referenceAspect = referenceResolution.x / referenceResolution.y;
if (Mathf.Abs(aspectRatio - referenceAspect) > 0.01f)
{
// 宽高比不同,需要调整
baseSize *= referenceAspect / aspectRatio;
}
return Mathf.Clamp(baseSize, minOrthographicSize, maxOrthographicSize);
}
/// <summary>
/// 设置透视2D效果
/// 模拟2.5D视觉效果
/// </summary>
private void SetupPerspective2D()
{
// 使用透视投影但限制在2D平面
targetCamera.orthographic = false;
// 设置视野角以匹配正交效果
float fov = Mathf.Atan2(targetCamera.orthographicSize, 10f) * 2f * Mathf.Rad2Deg;
targetCamera.fieldOfView = fov;
// 调整远裁剪平面
targetCamera.farClipPlane = 100f;
targetCamera.nearClipPlane = 0.1f;
}
/// <summary>
/// 应用自定义投影矩阵
/// 用于特殊效果如鱼眼、倾斜等
/// </summary>
private void ApplyCustomProjection()
{
// 获取标准投影矩阵
Matrix4x4 standardMatrix = targetCamera.projectionMatrix;
// 可以根据需要修改矩阵
// 例如:倾斜效果
if (Application.isEditor)
{
// 在编辑器中添加轻微倾斜以测试效果
ApplyTiltEffect(ref standardMatrix, 0.05f);
}
targetCamera.projectionMatrix = standardMatrix;
}
/// <summary>
/// 应用倾斜效果
/// </summary>
private void ApplyTiltEffect(ref Matrix4x4 matrix, float tiltAmount)
{
// 修改投影矩阵的倾斜分量
matrix[0, 1] = tiltAmount;
matrix[1, 0] = tiltAmount * 0.5f;
}
/// <summary>
/// 计算最佳正交大小
/// 考虑所有追踪目标
/// </summary>
private void CalculateOptimalOrthographicSize()
{
if (!enableDynamicZoom || trackingTargets.Count == 0)
return;
// 计算包围所有目标的边界框
Bounds combinedBounds = CalculateTargetBounds();
// 计算需要的摄像机大小
float requiredWidth = combinedBounds.size.x / targetCamera.aspect;
float requiredHeight = combinedBounds.size.y;
float requiredSize = Mathf.Max(requiredWidth, requiredHeight) / 2f;
// 添加边距
requiredSize *= 1.2f;
// 平滑过渡
float currentSize = targetCamera.orthographicSize;
float newSize = Mathf.Lerp(currentSize, requiredSize, Time.deltaTime * 2f);
targetCamera.orthographicSize = Mathf.Clamp(newSize, minOrthographicSize, maxOrthographicSize);
}
/// <summary>
/// 计算目标边界框
/// </summary>
private Bounds CalculateTargetBounds()
{
if (trackingTargets.Count == 0)
return new Bounds(transform.position, Vector3.zero);
Bounds bounds = new Bounds(trackingTargets[0].position, Vector3.zero);
foreach (Transform target in trackingTargets)
{
if (target != null)
{
bounds.Encapsulate(target.position);
// 考虑目标的边界框
Renderer renderer = target.GetComponent<Renderer>();
if (renderer != null)
{
bounds.Encapsulate(renderer.bounds);
}
}
}
return bounds;
}
/// <summary>
/// 更新摄像机位置
/// 平滑追踪多个目标
/// </summary>
private void UpdateCameraPosition()
{
if (trackingTargets.Count == 0)
return;
// 计算加权中心
Vector3 newPosition = CalculateWeightedCenter();
// 应用边界限制
if (useBounds)
{
newPosition = ClampToBounds(newPosition);
}
// 平滑移动
transform.position = Vector3.SmoothDamp(
transform.position,
newPosition,
ref cameraVelocity,
0.3f
);
// 更新正交大小(如果需要)
if (enableDynamicZoom)
{
CalculateOptimalOrthographicSize();
}
}
/// <summary>
/// 计算加权中心
/// 不同的目标可以有不同权重
/// </summary>
private Vector3 CalculateWeightedCenter()
{
if (trackingTargets.Count == 0)
return transform.position;
Vector3 center = Vector3.zero;
float totalWeight = 0f;
foreach (Transform target in trackingTargets)
{
if (target != null)
{
// 可以根据目标类型设置不同权重
float weight = GetTargetWeight(target);
center += target.position * weight;
totalWeight += weight;
}
}
if (totalWeight > 0)
{
center /= totalWeight;
}
// 保持Z轴不变
center.z = transform.position.z;
return center;
}
/// <summary>
/// 获取目标权重
/// </summary>
private float GetTargetWeight(Transform target)
{
// 默认权重为1
float weight = 1f;
// 可以根据标签、层或其他属性调整权重
if (target.CompareTag("Player"))
{
weight = 2f; // 玩家权重更高
}
else if (target.CompareTag("Boss"))
{
weight = 1.5f; // Boss中等权重
}
return weight;
}
/// <summary>
/// 限制在边界内
/// </summary>
private Vector3 ClampToBounds(Vector3 position)
{
// 考虑摄像机大小
float cameraHeight = targetCamera.orthographicSize * 2f;
float cameraWidth = cameraHeight * targetCamera.aspect;
float minX = cameraBounds.xMin + cameraWidth / 2f;
float maxX = cameraBounds.xMax - cameraWidth / 2f;
float minY = cameraBounds.yMin + cameraHeight / 2f;
float maxY = cameraBounds.yMax - cameraHeight / 2f;
position.x = Mathf.Clamp(position.x, minX, maxX);
position.y = Mathf.Clamp(position.y, minY, maxY);
return position;
}
/// <summary>
/// 添加追踪目标
/// </summary>
public void AddTrackingTarget(Transform target, float initialWeight = 1f)
{
if (!trackingTargets.Contains(target))
{
trackingTargets.Add(target);
// 可以在这里设置初始权重
if (initialWeight != 1f)
{
// 存储自定义权重
// 实际项目中可以用字典存储
}
}
}
/// <summary>
/// 移除追踪目标
/// </summary>
public void RemoveTrackingTarget(Transform target)
{
trackingTargets.Remove(target);
}
/// <summary>
/// 设置摄像机边界
/// </summary>
public void SetCameraBounds(Rect bounds)
{
cameraBounds = bounds;
useBounds = true;
}
/// <summary>
/// 触发屏幕震动
/// </summary>
public void ShakeScreen(float intensity, float duration)
{
shakeIntensity = intensity;
shakeDuration = duration;
originalPosition = transform.position;
}
/// <summary>
/// 处理屏幕震动
/// </summary>
private void HandleScreenShake()
{
if (shakeDuration > 0)
{
// 随机偏移
Vector3 shakeOffset = Random.insideUnitCircle * shakeIntensity;
transform.position = originalPosition + shakeOffset;
shakeDuration -= Time.deltaTime;
if (shakeDuration <= 0)
{
// 恢复原位置
transform.position = originalPosition;
}
}
}
/// <summary>
/// 应用摄像机效果
/// </summary>
private void ApplyCameraEffects()
{
// 这里可以添加各种后期效果
// 例如:动态模糊、颜色分级等
}
/// <summary>
/// 设置摄像机监听
/// </summary>
private void SetupCameraListeners()
{
// 监听分辨率变化
StartCoroutine(MonitorResolutionChanges());
// 监听屏幕旋转
StartCoroutine(MonitorOrientationChanges());
}
/// <summary>
/// 分辨率变化监控协程
/// </summary>
private System.Collections.IEnumerator MonitorResolutionChanges()
{
Vector2Int lastResolution = new Vector2Int(Screen.width, Screen.height);
while (true)
{
yield return new WaitForSeconds(0.5f);
Vector2Int currentResolution = new Vector2Int(Screen.width, Screen.height);
if (currentResolution != lastResolution)
{
lastResolution = currentResolution;
OnResolutionChanged(currentResolution);
}
}
}
/// <summary>
/// 分辨率变化处理
/// </summary>
private void OnResolutionChanged(Vector2Int newResolution)
{
// 重新计算正交大小
float newSize = CalculateOrthographicSize();
targetCamera.orthographicSize = newSize;
// 通知其他系统
BroadcastMessage("OnCameraResolutionChanged", newResolution,
SendMessageOptions.DontRequireReceiver);
}
/// <summary>
/// 保存摄像机状态
/// </summary>
public CameraState SaveState()
{
CameraState state = new CameraState
{
Position = transform.position,
OrthographicSize = targetCamera.orthographicSize,
Rotation = transform.eulerAngles.z,
ViewportRect = targetCamera.rect
};
return state;
}
/// <summary>
/// 恢复摄像机状态
/// </summary>
public void RestoreState(CameraState state)
{
transform.position = state.Position;
targetCamera.orthographicSize = state.OrthographicSize;
transform.rotation = Quaternion.Euler(0, 0, state.Rotation);
targetCamera.rect = state.ViewportRect;
}
}
}
7.1.2 Sprite渲染器的排序算法与优化
在2D游戏中,Sprite的正确排序对于视觉呈现至关重要。Unity使用Sorting Layer和Order in Layer来管理渲染顺序,但在商业项目中,我们需要更精细的控制和优化。
排序算法原理:
- 深度排序:基于Z轴或自定义深度值
- 层级排序:Sorting Layer的优先级
- 批次排序:相同材质的Sprite连续渲染
以下是一个高级Sprite排序管理器的实现:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace CommercialProject.Graphics2D
{
/// <summary>
/// 高级Sprite排序管理器
/// 支持动态深度计算和批量优化
/// </summary>
public class AdvancedSpriteSorter : MonoBehaviour
{
[SerializeField]
private SortingMethod sortingMethod = SortingMethod.YAxis;
[SerializeField]
private bool enableDynamicSorting = true;
[SerializeField]
private float sortingUpdateInterval = 0.1f;
[SerializeField]
private bool useCustomDepthCalculation = false;
// 自定义排序规则
public delegate int CustomSortRule(SpriteRenderer a, SpriteRenderer b);
private CustomSortRule customSortRule;
// 排序组管理
private Dictionary<string, List<SpriteRenderer>> sortingGroups;
/// <summary>
/// 排序方法枚举
/// </summary>
public enum SortingMethod
{
YAxis, // Y轴排序(越往下越在前)
ZAxis, // Z轴排序
CustomRule, // 自定义规则
Manual // 手动控制
}
/// <summary>
/// Sprite深度信息
/// </summary>
public class SpriteDepthInfo
{
public SpriteRenderer Renderer;
public float CalculatedDepth;
public int OriginalOrder;
public string GroupId;
public SpriteDepthInfo(SpriteRenderer renderer, float depth, string groupId)
{
Renderer = renderer;
CalculatedDepth = depth;
OriginalOrder = renderer.sortingOrder;
GroupId = groupId;
}
}
private void Start()
{
InitializeSortingSystem();
StartCoroutine(DynamicSortingUpdate());
}
/// <summary>
/// 初始化排序系统
/// </summary>
private void InitializeSortingSystem()
{
sortingGroups = new Dictionary<string, List<SpriteRenderer>>();
// 扫描场景中的所有SpriteRenderer
ScanSceneSprites();
// 设置默认排序规则
if (sortingMethod == SortingMethod.YAxis)
{
SetYAxisSortingRule();
}
else if (sortingMethod == SortingMethod.CustomRule && customSortRule == null)
{
SetDefaultCustomRule();
}
}
/// <summary>
/// 扫描场景Sprite
/// </summary>
private void ScanSceneSprites()
{
SpriteRenderer[] allSprites = FindObjectsOfType<SpriteRenderer>();
foreach (SpriteRenderer sprite in allSprites)
{
// 检查是否属于某个排序组
string groupId = GetSpriteGroup(sprite);
if (!sortingGroups.ContainsKey(groupId))
{
sortingGroups[groupId] = new List<SpriteRenderer>();
}
sortingGroups[groupId].Add(sprite);
// 如果使用自定义深度计算,计算初始深度
if (useCustomDepthCalculation)
{
CalculateCustomDepth(sprite);
}
}
}
/// <summary>
/// 获取Sprite所属分组
/// </summary>
private string GetSpriteGroup(SpriteRenderer sprite)
{
// 默认使用Sorting Layer作为分组
return sprite.sortingLayerName;
}
/// <summary>
/// 设置Y轴排序规则
/// </summary>
private void SetYAxisSortingRule()
{
customSortRule = (a, b) =>
{
// Y值越小(越靠下)的渲染顺序越靠前
float depthA = CalculateYAxisDepth(a.transform.position.y, a.bounds.size.y);
float depthB = CalculateYAxisDepth(b.transform.position.y, b.bounds.size.y);
return depthB.CompareTo(depthA); // 降序排列
};
}
/// <summary>
/// 计算Y轴深度
/// </summary>
private float CalculateYAxisDepth(float yPosition, float height)
{
// 基础深度基于Y坐标
float baseDepth = -yPosition; // Y越小(越往下)深度值越大
// 考虑Sprite高度,高的物体应该覆盖更多区域
baseDepth += height * 0.1f;
return baseDepth;
}
/// <summary>
/// 设置默认自定义规则
/// </summary>
private void SetDefaultCustomRule()
{
customSortRule = (a, b) =>
{
// 综合多种因素的排序规则
float scoreA = CalculateSpriteScore(a);
float scoreB = CalculateSpriteScore(b);
return scoreB.CompareTo(scoreA);
};
}
/// <summary>
/// 计算Sprite综合评分
/// </summary>
private float CalculateSpriteScore(SpriteRenderer sprite)
{
float score = 0f;
// 1. Y轴位置权重(50%)
score += (-sprite.transform.position.y) * 0.5f;
// 2. Z轴位置权重(30%)
score += sprite.transform.position.z * 0.3f;
// 3. 尺寸权重(20%)
score += sprite.bounds.size.magnitude * 0.2f;
return score;
}
/// <summary>
/// 计算自定义深度
/// </summary>
private void CalculateCustomDepth(SpriteRenderer sprite)
{
if (sprite == null)
return;
// 基于多种因素计算深度
float depth = 0f;
// 位置因素
depth += sprite.transform.position.y * -100f; // Y轴主要影响
depth += sprite.transform.position.z * 10f; // Z轴次要影响
// 尺寸因素
depth += sprite.bounds.size.y * 5f;
// 材质因素(如果有)
if (sprite.material != null)
{
// 可以根据材质属性调整深度
depth += sprite.material.GetInstanceID() % 1000 * 0.001f;
}
// 存储计算出的深度
sprite.sortingOrder = Mathf.RoundToInt(depth);
}
/// <summary>
/// 动态排序更新协程
/// </summary>
private System.Collections.IEnumerator DynamicSortingUpdate()
{
while (enableDynamicSorting)
{
yield return new WaitForSeconds(sortingUpdateInterval);
UpdateAllSpriteSorting();
}
}
/// <summary>
/// 更新所有Sprite排序
/// </summary>
private void UpdateAllSpriteSorting()
{
foreach (var group in sortingGroups)
{
UpdateGroupSorting(group.Key, group.Value);
}
}
/// <summary>
/// 更新分组排序
/// </summary>
private void UpdateGroupSorting(string groupId, List<SpriteRenderer> sprites)
{
if (sprites.Count <= 1)
return;
// 收集深度信息
List<SpriteDepthInfo> depthInfos = new List<SpriteDepthInfo>();
foreach (SpriteRenderer sprite in sprites)
{
if (sprite != null && sprite.isVisible)
{
float depth = CalculateDepthForSorting(sprite);
depthInfos.Add(new SpriteDepthInfo(sprite, depth, groupId));
}
}
// 根据选择的排序方法排序
switch (sortingMethod)
{
case SortingMethod.YAxis:
SortByYAxis(depthInfos);
break;
case SortingMethod.ZAxis:
SortByZAxis(depthInfos);
break;
case SortingMethod.CustomRule:
SortByCustomRule(depthInfos);
break;
}
// 应用排序结果
ApplySortingOrders(depthInfos);
// 优化渲染批次
OptimizeRenderBatches(depthInfos);
}
/// <summary>
/// 计算排序深度
/// </summary>
private float CalculateDepthForSorting(SpriteRenderer sprite)
{
switch (sortingMethod)
{
case SortingMethod.YAxis:
return sprite.transform.position.y;
case SortingMethod.ZAxis:
return sprite.transform.position.z;
case SortingMethod.CustomRule:
return CalculateSpriteScore(sprite);
default:
return sprite.sortingOrder;
}
}
/// <summary>
/// Y轴排序
/// </summary>
private void SortByYAxis(List<SpriteDepthInfo> depthInfos)
{
depthInfos.Sort((a, b) =>
{
// 考虑Sprite高度
float depthA = a.Renderer.transform.position.y - a.Renderer.bounds.extents.y;
float depthB = b.Renderer.transform.position.y - b.Renderer.bounds.extents.y;
return depthA.CompareTo(depthB); // 升序:Y值小的在前
});
}
/// <summary>
/// Z轴排序
/// </summary>
private void SortByZAxis(List<SpriteDepthInfo> depthInfos)
{
depthInfos.Sort((a, b) =>
{
return a.Renderer.transform.position.z.CompareTo(b.Renderer.transform.position.z);
});
}
/// <summary>
/// 自定义规则排序
/// </summary>
private void SortByCustomRule(List<SpriteDepthInfo> depthInfos)
{
if (customSortRule != null)
{
depthInfos.Sort((a, b) =>
{
return customSortRule(a.Renderer, b.Renderer);
});
}
}
/// <summary>
/// 应用排序顺序
/// </summary>
private void ApplySortingOrders(List<SpriteDepthInfo> depthInfos)
{
for (int i = 0; i < depthInfos.Count; i++)
{
depthInfos[i].Renderer.sortingOrder = i;
}
}
/// <summary>
/// 优化渲染批次
/// 相同材质的Sprite连续渲染
/// </summary>
private void OptimizeRenderBatches(List<SpriteDepthInfo> depthInfos)
{
// 按材质分组
var materialGroups = depthInfos
.GroupBy(info => info.Renderer.sharedMaterial)
.OrderBy(group => group.Key.GetInstanceID());
int currentOrder = 0;
foreach (var materialGroup in materialGroups)
{
// 对同一材质的Sprite按深度排序
var sortedInGroup = materialGroup
.OrderBy(info => info.CalculatedDepth)
.ToList();
// 分配连续的顺序
foreach (var info in sortedInGroup)
{
info.Renderer.sortingOrder = currentOrder++;
}
}
}
/// <summary>
/// 手动排序Sprite
/// 用于特定场景或性能敏感区域
/// </summary>
public void ManualSortSprite(SpriteRenderer sprite, int manualOrder)
{
if (sprite == null)
return;
// 暂时禁用动态排序
bool wasDynamic = enableDynamicSorting;
enableDynamicSorting = false;
// 应用手动顺序
sprite.sortingOrder = manualOrder;
// 恢复原状态
StartCoroutine(RestoreDynamicSorting(wasDynamic, 1f));
}
/// <summary>
/// 恢复动态排序协程
/// </summary>
private System.Collections.IEnumerator RestoreDynamicSorting(bool shouldEnable, float delay)
{
yield return new WaitForSeconds(delay);
enableDynamicSorting = shouldEnable;
}
/// <summary>
/// 注册Sprite到排序系统
/// </summary>
public void RegisterSprite(SpriteRenderer sprite, string groupId = "Default")
{
if (sprite == null)
return;
if (!sortingGroups.ContainsKey(groupId))
{
sortingGroups[groupId] = new List<SpriteRenderer>();
}
if (!sortingGroups[groupId].Contains(sprite))
{
sortingGroups[groupId].Add(sprite);
// 立即计算一次深度
if (useCustomDepthCalculation)
{
CalculateCustomDepth(sprite);
}
}
}
/// <summary>
/// 取消注册Sprite
/// </summary>
public void UnregisterSprite(SpriteRenderer sprite)
{
foreach (var group in sortingGroups)
{
group.Value.Remove(sprite);
}
}
/// <summary>
/// 设置自定义排序规则
/// </summary>
public void SetCustomSortRule(CustomSortRule rule)
{
customSortRule = rule;
sortingMethod = SortingMethod.CustomRule;
}
/// <summary>
/// 强制立即更新排序
/// </summary>
public void ForceSortingUpdate()
{
UpdateAllSpriteSorting();
}
/// <summary>
/// 获取Sprite的当前深度信息
/// </summary>
public SpriteDepthInfo GetSpriteDepthInfo(SpriteRenderer sprite)
{
foreach (var group in sortingGroups)
{
foreach (var s in group.Value)
{
if (s == sprite)
{
float depth = CalculateDepthForSorting(sprite);
return new SpriteDepthInfo(sprite, depth, group.Key);
}
}
}
return null;
}
}
}
7.1.3 Sprite裁切的几何算法与优化
Sprite裁切(Cropping)是2D游戏开发中常见的需求,特别是在实现血条、进度条、遮罩效果时。裁切涉及到纹理坐标的计算和几何变换。
纹理裁切数学原理:
裁切UV = 原始UV × 裁切比例 + 裁切偏移
裁切比例 = 目标尺寸 / 原始尺寸
裁切偏移 = 裁切起始位置 / 原始尺寸
以下是一个高级Sprite裁切系统的实现:
using UnityEngine;
using System.Collections.Generic;
namespace CommercialProject.Graphics2D
{
/// <summary>
/// 高级Sprite裁切系统
/// 支持多种裁切模式和动态效果
/// </summary>
public class AdvancedSpriteCropper : MonoBehaviour
{
[SerializeField]
private SpriteRenderer targetRenderer;
[SerializeField]
private CropMode cropMode = CropMode.Horizontal;
[SerializeField]
[Range(0, 1)]
private float cropAmount = 1f;
[SerializeField]
private bool useWorldSpace = false;
[SerializeField]
private bool enableAnimation = false;
[SerializeField]
private float animationSpeed = 1f;
// 材质属性ID缓存
private static readonly int CropAmountId = Shader.PropertyToID("_CropAmount");
private static readonly int CropDirectionId = Shader.PropertyToID("_CropDirection");
private static readonly int CropCenterId = Shader.PropertyToID("_CropCenter");
// 动画状态
private float targetCropAmount;
private float animationVelocity;
// 自定义裁切区域
private Rect customCropRect = new Rect(0, 0, 1, 1);
/// <summary>
/// 裁切模式枚举
/// </summary>
public enum CropMode
{
Horizontal, // 水平裁切
Vertical, // 垂直裁切
Radial, // 径向裁切
Custom, // 自定义矩形
Mask // 遮罩裁切
}
/// <summary>
/// 裁切动画曲线
/// </summary>
public AnimationCurve cropAnimationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
private void Start()
{
InitializeCropSystem();
if (enableAnimation)
{
targetCropAmount = cropAmount;
StartCoroutine(AnimateCrop());
}
}
private void Update()
{
if (enableAnimation)
{
UpdateCropAnimation();
}
}
/// <summary>
/// 初始化裁切系统
/// </summary>
private void InitializeCropSystem()
{
if (targetRenderer == null)
{
targetRenderer = GetComponent<SpriteRenderer>();
}
// 确保有材质实例
if (targetRenderer != null)
{
targetRenderer.material = new Material(targetRenderer.material);
// 设置初始裁切参数
ApplyCropParameters();
}
}
/// <summary>
/// 应用裁切参数
/// </summary>
private void ApplyCropParameters()
{
if (targetRenderer == null || targetRenderer.material == null)
return;
Material material = targetRenderer.material;
// 设置基本参数
material.SetFloat(CropAmountId, cropAmount);
// 根据模式设置方向
Vector2 direction = Vector2.zero;
Vector2 center = Vector2.zero;
switch (cropMode)
{
case CropMode.Horizontal:
direction = Vector2.right;
center = new Vector2(0.5f, 0.5f);
break;
case CropMode.Vertical:
direction = Vector2.up;
center = new Vector2(0.5f, 0.5f);
break;
case CropMode.Radial:
direction = Vector2.one.normalized;
center = new Vector2(0.5f, 0.5f);
break;
case CropMode.Custom:
// 自定义矩形裁切
ApplyCustomCrop(material);
return;
case CropMode.Mask:
// 遮罩裁切需要额外的设置
ApplyMaskCrop(material);
return;
}
material.SetVector(CropDirectionId, direction);
material.SetVector(CropCenterId, center);
}
/// <summary>
/// 应用自定义裁切
/// </summary>
private void ApplyCustomCrop(Material material)
{
// 自定义矩形裁切使用四个参数
material.SetVector("_CropRect", new Vector4(
customCropRect.x,
customCropRect.y,
customCropRect.width,
customCropRect.height
));
}
/// <summary>
/// 应用遮罩裁切
/// </summary>
private void ApplyMaskCrop(Material material)
{
// 遮罩裁切需要遮罩纹理
if (material.HasProperty("_MaskTex"))
{
// 可以在这里设置遮罩纹理
// material.SetTexture("_MaskTex", maskTexture);
}
}
/// <summary>
/// 设置裁切比例
/// </summary>
public void SetCropAmount(float amount)
{
cropAmount = Mathf.Clamp01(amount);
if (enableAnimation)
{
targetCropAmount = cropAmount;
}
else
{
ApplyCropParameters();
}
}
/// <summary>
/// 设置裁切模式
/// </summary>
public void SetCropMode(CropMode mode)
{
cropMode = mode;
ApplyCropParameters();
}
/// <summary>
/// 设置自定义裁切区域
/// </summary>
public void SetCustomCropRect(Rect rect)
{
customCropRect = rect;
cropMode = CropMode.Custom;
ApplyCropParameters();
}
/// <summary>
/// 更新裁切动画
/// </summary>
private void UpdateCropAnimation()
{
if (Mathf.Abs(cropAmount - targetCropAmount) > 0.001f)
{
cropAmount = Mathf.SmoothDamp(
cropAmount,
targetCropAmount,
ref animationVelocity,
0.3f / animationSpeed
);
ApplyCropParameters();
}
}
/// <summary>
/// 裁切动画协程
/// </summary>
private System.Collections.IEnumerator AnimateCrop()
{
while (true)
{
// 这里可以添加周期性动画效果
yield return null;
}
}
/// <summary>
/// 基于世界坐标的裁切
/// 例如:根据其他物体的位置进行裁切
/// </summary>
public void SetWorldSpaceCrop(Vector3 worldPosition, float influenceRadius)
{
if (!useWorldSpace)
return;
// 计算世界位置到本地位置的转换
Vector3 localPosition = transform.InverseTransformPoint(worldPosition);
// 计算裁切参数
float distance = localPosition.magnitude;
float cropValue = Mathf.Clamp01(distance / influenceRadius);
SetCropAmount(cropValue);
}
/// <summary>
/// 创建进度条裁切效果
/// </summary>
public void CreateProgressBarCrop(float progress, ProgressDirection direction = ProgressDirection.RightToLeft)
{
cropMode = CropMode.Horizontal;
// 根据方向调整裁切
switch (direction)
{
case ProgressDirection.LeftToRight:
SetCropAmount(progress);
break;
case ProgressDirection.RightToLeft:
SetCropAmount(1 - progress);
break;
case ProgressDirection.TopToBottom:
cropMode = CropMode.Vertical;
SetCropAmount(1 - progress);
break;
case ProgressDirection.BottomToTop:
cropMode = CropMode.Vertical;
SetCropAmount(progress);
break;
case ProgressDirection.RadialCW:
cropMode = CropMode.Radial;
SetCropAmount(progress);
// 需要设置旋转方向
break;
case ProgressDirection.RadialCCW:
cropMode = CropMode.Radial;
SetCropAmount(progress);
// 需要设置旋转方向
break;
}
ApplyCropParameters();
}
public enum ProgressDirection
{
LeftToRight,
RightToLeft,
TopToBottom,
BottomToTop,
RadialCW,
RadialCCW
}
/// <summary>
/// 创建遮罩裁切效果
/// </summary>
public void CreateMaskCrop(Texture2D maskTexture, bool invertMask = false)
{
if (targetRenderer == null || targetRenderer.material == null)
return;
cropMode = CropMode.Mask;
Material material = targetRenderer.material;
// 设置遮罩纹理
if (material.HasProperty("_MaskTex"))
{
material.SetTexture("_MaskTex", maskTexture);
material.SetFloat("_InvertMask", invertMask ? 1 : 0);
}
ApplyCropParameters();
}
/// <summary>
/// 像素精确裁切
/// 确保裁切边缘在像素边界上
/// </summary>
public void SetPixelPerfectCrop(float amount, int textureWidth)
{
// 计算最接近的像素边界
int pixelCrop = Mathf.RoundToInt(amount * textureWidth);
float perfectAmount = pixelCrop / (float)textureWidth;
SetCropAmount(perfectAmount);
}
/// <summary>
/// 批量裁切多个Sprite
/// </summary>
public static void BatchCropSprites(SpriteRenderer[] sprites, float amount, CropMode mode)
{
foreach (SpriteRenderer sprite in sprites)
{
var cropper = sprite.GetComponent<AdvancedSpriteCropper>();
if (cropper == null)
{
cropper = sprite.gameObject.AddComponent<AdvancedSpriteCropper>();
}
cropper.SetCropMode(mode);
cropper.SetCropAmount(amount);
}
}
/// <summary>
/// 保存裁切状态
/// </summary>
public CropState SaveCropState()
{
return new CropState
{
CropAmount = cropAmount,
Mode = cropMode,
CustomRect = customCropRect,
UseWorldSpace = useWorldSpace
};
}
/// <summary>
/// 恢复裁切状态
/// </summary>
public void RestoreCropState(CropState state)
{
cropAmount = state.CropAmount;
cropMode = state.Mode;
customCropRect = state.CustomRect;
useWorldSpace = state.UseWorldSpace;
ApplyCropParameters();
}
/// <summary>
/// 裁切状态结构
/// </summary>
[System.Serializable]
public struct CropState
{
public float CropAmount;
public CropMode Mode;
public Rect CustomRect;
public bool UseWorldSpace;
}
}
}
7.2 Sprite动画的数学插值与控制
7.2.1 2D动画的创建与关键帧插值算法
2D动画的核心是帧插值(Interpolation)。在商业级2D游戏中,我们不仅需要基本的帧动画,还需要复杂的插值算法来实现平滑的动画效果。
插值算法分类:
- 线性插值(Lerp):最简单的插值方式
- 样条插值(Spline):使用贝塞尔曲线或Catmull-Rom曲线
- 球形插值(Slerp):用于旋转插值
- 缓动函数(Easing):各种数学缓动曲线
以下是一个高级2D动画系统的实现:
using UnityEngine;
using System.Collections.Generic;
namespace CommercialProject.Animation2D
{
/// <summary>
/// 高级2D动画系统
/// 支持多种插值算法和动画混合
/// </summary>
public class AdvancedSpriteAnimator : MonoBehaviour
{
[SerializeField]
private SpriteRenderer targetRenderer;
[SerializeField]
private List<AnimationClip2D> animationClips;
[SerializeField]
private bool playOnStart = true;
[SerializeField]
private string defaultClipName = "Idle";
// 动画状态
private AnimationClip2D currentClip;
private float currentTime;
private bool isPlaying;
private float playbackSpeed = 1f;
// 插值算法
private InterpolationMethod interpolationMethod = InterpolationMethod.Linear;
// 动画事件
public event System.Action<string> OnAnimationEvent;
public event System.Action OnAnimationComplete;
/// <summary>
/// 2D动画剪辑
/// </summary>
[System.Serializable]
public class AnimationClip2D
{
public string ClipName;
public Sprite[] Frames;
public float FrameRate = 12f;
public bool Loop = true;
public AnimationEvent2D[] Events;
// 计算属性
public float FrameDuration => 1f / FrameRate;
public float TotalDuration => Frames.Length * FrameDuration;
/// <summary>
/// 获取指定时间的Sprite
/// </summary>
public Sprite GetSpriteAtTime(float time)
{
if (Frames == null || Frames.Length == 0)
return null;
if (Loop)
{
time %= TotalDuration;
}
else
{
time = Mathf.Clamp(time, 0, TotalDuration);
}
float frameFloat = time / FrameDuration;
int frameIndex = Mathf.FloorToInt(frameFloat);
if (frameIndex >= Frames.Length)
{
frameIndex = Frames.Length - 1;
}
return Frames[frameIndex];
}
}
/// <summary>
/// 动画事件
/// </summary>
[System.Serializable]
public class AnimationEvent2D
{
public string EventName;
public float TriggerTime;
public bool HasTriggered;
}
/// <summary>
/// 插值方法枚举
/// </summary>
public enum InterpolationMethod
{
Linear,
EaseInOut,
Spring,
Bounce,
CustomCurve
}
private void Start()
{
InitializeAnimator();
if (playOnStart && !string.IsNullOrEmpty(defaultClipName))
{
Play(defaultClipName);
}
}
private void Update()
{
if (isPlaying && currentClip != null)
{
UpdateAnimation(Time.deltaTime);
}
}
/// <summary>
/// 初始化动画器
/// </summary>
private void InitializeAnimator()
{
if (targetRenderer == null)
{
targetRenderer = GetComponent<SpriteRenderer>();
}
// 验证动画剪辑
ValidateAnimationClips();
}
/// <summary>
/// 验证动画剪辑
/// </summary>
private void ValidateAnimationClips()
{
foreach (var clip in animationClips)
{
if (clip.Frames == null || clip.Frames.Length == 0)
{
Debug.LogWarning($"Animation clip '{clip.ClipName}' has no frames!");
}
}
}
/// <summary>
/// 播放动画
/// </summary>
public void Play(string clipName, float speed = 1f)
{
AnimationClip2D clip = FindClipByName(clipName);
if (clip == null)
{
Debug.LogWarning($"Animation clip '{clipName}' not found!");
return;
}
currentClip = clip;
currentTime = 0f;
playbackSpeed = speed;
isPlaying = true;
// 重置事件触发状态
ResetEventStates();
// 立即更新第一帧
UpdateSprite();
}
/// <summary>
/// 通过名称查找剪辑
/// </summary>
private AnimationClip2D FindClipByName(string name)
{
foreach (var clip in animationClips)
{
if (clip.ClipName == name)
{
return clip;
}
}
return null;
}
/// <summary>
/// 重置事件状态
/// </summary>
private void ResetEventStates()
{
if (currentClip?.Events != null)
{
foreach (var evt in currentClip.Events)
{
evt.HasTriggered = false;
}
}
}
/// <summary>
/// 更新动画
/// </summary>
private void UpdateAnimation(float deltaTime)
{
// 应用播放速度
float scaledDeltaTime = deltaTime * playbackSpeed;
// 更新动画时间
float previousTime = currentTime;
currentTime += scaledDeltaTime;
// 检查动画事件
CheckAnimationEvents(previousTime, currentTime);
// 更新Sprite
UpdateSprite();
// 检查动画是否完成
if (!currentClip.Loop && currentTime >= currentClip.TotalDuration)
{
OnAnimationComplete?.Invoke();
// 可以在这里触发下一个动画或停止
if (animationClips.Count > 0)
{
// 自动切换到默认动画
Play(defaultClipName);
}
else
{
isPlaying = false;
}
}
}
/// <summary>
/// 检查动画事件
/// </summary>
private void CheckAnimationEvents(float previousTime, float currentTime)
{
if (currentClip?.Events == null)
return;
foreach (var evt in currentClip.Events)
{
if (!evt.HasTriggered &&
currentTime >= evt.TriggerTime &&
previousTime < evt.TriggerTime)
{
evt.HasTriggered = true;
OnAnimationEvent?.Invoke(evt.EventName);
}
}
}
/// <summary>
/// 更新Sprite
/// 应用插值算法
/// </summary>
private void UpdateSprite()
{
if (currentClip == null || targetRenderer == null)
return;
Sprite currentSprite = currentClip.GetSpriteAtTime(currentTime);
if (currentSprite != null)
{
targetRenderer.sprite = currentSprite;
}
}
/// <summary>
/// 停止动画
/// </summary>
public void Stop()
{
isPlaying = false;
currentTime = 0f;
}
/// <summary>
/// 暂停动画
/// </summary>
public void Pause()
{
isPlaying = false;
}
/// <summary>
/// 恢复动画
/// </summary>
public void Resume()
{
if (currentClip != null)
{
isPlaying = true;
}
}
/// <summary>
/// 设置播放速度
/// </summary>
public void SetPlaybackSpeed(float speed)
{
playbackSpeed = speed;
}
/// <summary>
/// 跳转到指定时间
/// </summary>
public void Seek(float time)
{
if (currentClip != null)
{
currentTime = Mathf.Clamp(time, 0, currentClip.TotalDuration);
UpdateSprite();
}
}
/// <summary>
/// 跳转到指定帧
/// </summary>
public void SeekFrame(int frameIndex)
{
if (currentClip != null && currentClip.Frames != null)
{
frameIndex = Mathf.Clamp(frameIndex, 0, currentClip.Frames.Length - 1);
currentTime = frameIndex * currentClip.FrameDuration;
UpdateSprite();
}
}
/// <summary>
/// 获取当前动画进度(0-1)
/// </summary>
public float GetNormalizedTime()
{
if (currentClip == null)
return 0f;
return currentTime / currentClip.TotalDuration;
}
/// <summary>
/// 添加动画剪辑
/// </summary>
public void AddAnimationClip(AnimationClip2D clip)
{
if (FindClipByName(clip.ClipName) == null)
{
animationClips.Add(clip);
}
else
{
Debug.LogWarning($"Animation clip '{clip.ClipName}' already exists!");
}
}
/// <summary>
/// 移除动画剪辑
/// </summary>
public void RemoveAnimationClip(string clipName)
{
AnimationClip2D clip = FindClipByName(clipName);
if (clip != null)
{
animationClips.Remove(clip);
if (currentClip == clip)
{
currentClip = null;
isPlaying = false;
}
}
}
/// <summary>
/// 创建动画事件
/// </summary>
public void AddAnimationEvent(string clipName, string eventName, float triggerTime)
{
AnimationClip2D clip = FindClipByName(clipName);
if (clip != null)
{
if (clip.Events == null)
{
clip.Events = new AnimationEvent2D[0];
}
var newEvent = new AnimationEvent2D
{
EventName = eventName,
TriggerTime = triggerTime,
HasTriggered = false
};
var eventList = new List<AnimationEvent2D>(clip.Events);
eventList.Add(newEvent);
clip.Events = eventList.ToArray();
}
}
/// <summary>
/// 交叉淡入淡出动画
/// </summary>
public void CrossFade(string toClipName, float fadeDuration)
{
if (!isPlaying || currentClip == null)
{
Play(toClipName);
return;
}
StartCoroutine(CrossFadeCoroutine(toClipName, fadeDuration));
}
/// <summary>
/// 交叉淡入淡出协程
/// </summary>
private System.Collections.IEnumerator CrossFadeCoroutine(string toClipName, float fadeDuration)
{
AnimationClip2D fromClip = currentClip;
AnimationClip2D toClip = FindClipByName(toClipName);
if (toClip == null)
yield break;
// 创建临时SpriteRenderer用于混合
GameObject tempObject = new GameObject("TempSprite");
tempObject.transform.SetParent(transform);
tempObject.transform.localPosition = Vector3.zero;
SpriteRenderer tempRenderer = tempObject.AddComponent<SpriteRenderer>();
tempRenderer.sprite = targetRenderer.sprite;
tempRenderer.sortingOrder = targetRenderer.sortingOrder - 1;
// 播放新动画
Play(toClipName);
// 淡出旧动画
float elapsedTime = 0f;
while (elapsedTime < fadeDuration)
{
elapsedTime += Time.deltaTime;
float t = elapsedTime / fadeDuration;
// 更新透明度
Color color = tempRenderer.color;
color.a = 1f - t;
tempRenderer.color = color;
yield return null;
}
// 清理临时对象
Destroy(tempObject);
}
/// <summary>
/// 获取当前动画信息
/// </summary>
public AnimationInfo GetCurrentAnimationInfo()
{
return new AnimationInfo
{
ClipName = currentClip?.ClipName,
IsPlaying = isPlaying,
CurrentTime = currentTime,
NormalizedTime = GetNormalizedTime(),
FrameIndex = GetCurrentFrameIndex()
};
}
/// <summary>
/// 获取当前帧索引
/// </summary>
private int GetCurrentFrameIndex()
{
if (currentClip == null)
return -1;
return Mathf.FloorToInt(currentTime / currentClip.FrameDuration);
}
/// <summary>
/// 动画信息结构
/// </summary>
public struct AnimationInfo
{
public string ClipName;
public bool IsPlaying;
public float CurrentTime;
public float NormalizedTime;
public int FrameIndex;
}
}
}
7.2.2 2D动画控制器的状态机与混合树
在商业级2D游戏中,复杂的角色动画需要状态机(State Machine)和动画混合(Animation Blending)系统。这些系统基于图论和插值数学。
动画混合原理:
- 线性混合:
result = animation1 * weight1 + animation2 * weight2 - 加法混合:
result = base + additive - 同步混合:确保动画时间同步
以下是一个高级2D动画控制器的实现:
using UnityEngine;
using System.Collections.Generic;
namespace CommercialProject.Animation2D
{
/// <summary>
/// 高级2D动画控制器
/// 支持状态机、混合树和图层系统
/// </summary>
public class AdvancedAnimationController2D : MonoBehaviour
{
[SerializeField]
private SpriteRenderer targetRenderer;
[SerializeField]
private AnimationStateMachine stateMachine;
[SerializeField]
private List<AnimationLayer> animationLayers;
[SerializeField]
private bool updateRootMotion = false;
// 当前状态
private AnimationState currentState;
private float stateTransitionTime;
private float stateTransitionDuration;
// 混合树
private BlendTree2D currentBlendTree;
private Vector2 blendParameters;
/// <summary>
/// 动画状态机
/// </summary>
[System.Serializable]
public class AnimationStateMachine
{
public List<AnimationState> States;
public List<AnimationTransition> Transitions;
public string DefaultStateName;
/// <summary>
/// 查找状态
/// </summary>
public AnimationState FindState(string stateName)
{
foreach (var state in States)
{
if (state.StateName == stateName)
{
return state;
}
}
return null;
}
/// <summary>
/// 获取状态转换
/// </summary>
public AnimationTransition[] GetTransitionsFromState(string fromState)
{
List<AnimationTransition> result = new List<AnimationTransition>();
foreach (var transition in Transitions)
{
if (transition.FromState == fromState)
{
result.Add(transition);
}
}
return result.ToArray();
}
}
/// <summary>
/// 动画状态
/// </summary>
[System.Serializable]
public class AnimationState
{
public string StateName;
public AdvancedSpriteAnimator.AnimationClip2D AnimationClip;
public float Speed = 1f;
public bool Loop = true;
public AnimationBlendMode BlendMode = AnimationBlendMode.Override;
public float TransitionDuration = 0.2f;
// 根运动数据
public Vector2 RootMotionDelta;
public bool HasRootMotion;
}
/// <summary>
/// 动画转换
/// </summary>
[System.Serializable]
public class AnimationTransition
{
public string FromState;
public string ToState;
public TransitionCondition[] Conditions;
public float TransitionDuration = 0.2f;
public bool HasExitTime;
public float ExitTime = 0.8f;
/// <summary>
/// 检查条件是否满足
/// </summary>
public bool CheckConditions(AnimationParameters parameters)
{
foreach (var condition in Conditions)
{
if (!condition.IsMet(parameters))
{
return false;
}
}
return true;
}
}
/// <summary>
/// 转换条件
/// </summary>
[System.Serializable]
public class TransitionCondition
{
public string ParameterName;
public ConditionType Type;
public float FloatValue;
public bool BoolValue;
public int IntValue;
public enum ConditionType
{
FloatGreater,
FloatLess,
BoolEquals,
IntGreater,
IntLess
}
public bool IsMet(AnimationParameters parameters)
{
AnimationParameter parameter = parameters.GetParameter(ParameterName);
if (parameter == null)
return false;
switch (Type)
{
case ConditionType.FloatGreater:
return parameter.FloatValue > FloatValue;
case ConditionType.FloatLess:
return parameter.FloatValue < FloatValue;
case ConditionType.BoolEquals:
return parameter.BoolValue == BoolValue;
case ConditionType.IntGreater:
return parameter.IntValue > IntValue;
case ConditionType.IntLess:
return parameter.IntValue < IntValue;
default:
return false;
}
}
}
/// <summary>
/// 动画参数
/// </summary>
[System.Serializable]
public class AnimationParameters
{
private Dictionary<string, AnimationParameter> parameters;
public AnimationParameters()
{
parameters = new Dictionary<string, AnimationParameter>();
}
public void SetFloat(string name, float value)
{
if (!parameters.ContainsKey(name))
{
parameters[name] = new AnimationParameter(name);
}
parameters[name].FloatValue = value;
}
public void SetBool(string name, bool value)
{
if (!parameters.ContainsKey(name))
{
parameters[name] = new AnimationParameter(name);
}
parameters[name].BoolValue = value;
}
public void SetInt(string name, int value)
{
if (!parameters.ContainsKey(name))
{
parameters[name] = new AnimationParameter(name);
}
parameters[name].IntValue = value;
}
public AnimationParameter GetParameter(string name)
{
if (parameters.ContainsKey(name))
{
return parameters[name];
}
return null;
}
}
/// <summary>
/// 动画参数类
/// </summary>
[System.Serializable]
public class AnimationParameter
{
public string Name;
public float FloatValue;
public bool BoolValue;
public int IntValue;
public AnimationParameter(string name)
{
Name = name;
}
}
/// <summary>
/// 动画混合模式
/// </summary>
public enum AnimationBlendMode
{
Override,
Additive,
Multiply
}
/// <summary>
/// 动画图层
/// </summary>
[System.Serializable]
public class AnimationLayer
{
public string LayerName;
public float Weight = 1f;
public AnimationBlendMode BlendMode = AnimationBlendMode.Override;
public AvatarMask Mask;
public AnimationState CurrentState;
// 用于混合
public AnimationState PreviousState;
public float TransitionProgress;
}
/// <summary>
/// 2D混合树
/// </summary>
[System.Serializable]
public class BlendTree2D
{
public string TreeName;
public BlendTreeMotion[] Motions;
public Vector2 ParameterRange = new Vector2(-1, 1);
/// <summary>
/// 获取混合后的动画
/// </summary>
public AdvancedSpriteAnimator.AnimationClip2D GetBlendedClip(Vector2 parameters)
{
if (Motions == null || Motions.Length == 0)
return null;
if (Motions.Length == 1)
return Motions[0].Clip;
// 计算每个Motion的权重
float[] weights = new float[Motions.Length];
float totalWeight = 0f;
for (int i = 0; i < Motions.Length; i++)
{
float distance = Vector2.Distance(parameters, Motions[i].Position);
float weight = 1f / (distance + 0.001f); // 防止除零
weights[i] = weight;
totalWeight += weight;
}
// 归一化权重
for (int i = 0; i < weights.Length; i++)
{
weights[i] /= totalWeight;
}
// 创建混合后的剪辑(简化实现)
// 实际项目中需要更复杂的混合算法
return Motions[0].Clip; // 简化返回第一个
}
}
/// <summary>
/// 混合树Motion
/// </summary>
[System.Serializable]
public class BlendTreeMotion
{
public AdvancedSpriteAnimator.AnimationClip2D Clip;
public Vector2 Position;
public float Threshold = 0.1f;
}
// 动画参数实例
private AnimationParameters animationParameters;
private void Start()
{
InitializeController();
// 进入默认状态
if (!string.IsNullOrEmpty(stateMachine.DefaultStateName))
{
TransitionToState(stateMachine.DefaultStateName, 0f);
}
}
private void Update()
{
UpdateStateMachine();
UpdateAnimationLayers();
ApplyRootMotion();
}
/// <summary>
/// 初始化控制器
/// </summary>
private void InitializeController()
{
if (targetRenderer == null)
{
targetRenderer = GetComponent<SpriteRenderer>();
}
animationParameters = new AnimationParameters();
// 初始化图层
if (animationLayers == null)
{
animationLayers = new List<AnimationLayer>();
// 创建基础层
AnimationLayer baseLayer = new AnimationLayer
{
LayerName = "Base Layer",
Weight = 1f
};
animationLayers.Add(baseLayer);
}
}
/// <summary>
/// 更新状态机
/// </summary>
private void UpdateStateMachine()
{
if (currentState == null)
return;
// 检查状态转换
CheckStateTransitions();
// 更新状态过渡
UpdateStateTransition();
}
/// <summary>
/// 检查状态转换
/// </summary>
private void CheckStateTransitions()
{
if (stateTransitionTime < stateTransitionDuration)
return; // 正在过渡中
AnimationTransition[] transitions = stateMachine.GetTransitionsFromState(currentState.StateName);
foreach (var transition in transitions)
{
// 检查退出时间
if (transition.HasExitTime)
{
// 需要获取动画的标准化时间
// 这里简化处理
}
// 检查条件
if (transition.CheckConditions(animationParameters))
{
TransitionToState(transition.ToState, transition.TransitionDuration);
break;
}
}
}
/// <summary>
/// 转换到状态
/// </summary>
public void TransitionToState(string stateName, float transitionDuration)
{
AnimationState newState = stateMachine.FindState(stateName);
if (newState == null)
{
Debug.LogWarning($"State '{stateName}' not found!");
return;
}
// 设置过渡
if (currentState != null)
{
stateTransitionTime = 0f;
stateTransitionDuration = transitionDuration;
}
// 更新当前状态
currentState = newState;
// 更新基础层
if (animationLayers.Count > 0)
{
animationLayers[0].PreviousState = animationLayers[0].CurrentState;
animationLayers[0].CurrentState = newState;
animationLayers[0].TransitionProgress = 0f;
}
}
/// <summary>
/// 更新状态过渡
/// </summary>
private void UpdateStateTransition()
{
if (stateTransitionTime < stateTransitionDuration)
{
stateTransitionTime += Time.deltaTime;
// 更新基础层过渡进度
if (animationLayers.Count > 0)
{
animationLayers[0].TransitionProgress =
Mathf.Clamp01(stateTransitionTime / stateTransitionDuration);
}
}
}
/// <summary>
/// 更新动画图层
/// </summary>
private void UpdateAnimationLayers()
{
// 基础层始终存在
if (animationLayers.Count == 0)
return;
// 更新基础层
UpdateLayer(animationLayers[0]);
// 如果有更多图层,混合它们
for (int i = 1; i < animationLayers.Count; i++)
{
UpdateLayer(animationLayers[i]);
BlendLayerWithBase(animationLayers[i]);
}
}
/// <summary>
/// 更新图层
/// </summary>
private void UpdateLayer(AnimationLayer layer)
{
if (layer.CurrentState == null)
return;
// 更新过渡进度
if (layer.TransitionProgress < 1f)
{
layer.TransitionProgress += Time.deltaTime / layer.CurrentState.TransitionDuration;
layer.TransitionProgress = Mathf.Clamp01(layer.TransitionProgress);
}
}
/// <summary>
/// 图层与基础层混合
/// </summary>
private void BlendLayerWithBase(AnimationLayer layer)
{
if (layer.CurrentState == null || layer.Weight <= 0f)
return;
// 根据混合模式混合动画
switch (layer.BlendMode)
{
case AnimationBlendMode.Additive:
// 加法混合:基础层 + 当前层
ApplyAdditiveBlend(layer);
break;
case AnimationBlendMode.Multiply:
// 乘法混合:基础层 × 当前层
ApplyMultiplicativeBlend(layer);
break;
case AnimationBlendMode.Override:
// 覆盖混合:使用当前层覆盖基础层
ApplyOverrideBlend(layer);
break;
}
}
/// <summary>
/// 应用加法混合
/// </summary>
private void ApplyAdditiveBlend(AnimationLayer layer)
{
// 在实际项目中,这里需要实现动画的加法混合
// 这通常涉及对Sprite的变换进行加法操作
}
/// <summary>
/// 设置动画参数
/// </summary>
public void SetFloat(string name, float value)
{
animationParameters.SetFloat(name, value);
// 如果使用混合树,更新混合参数
if (currentBlendTree != null)
{
// 根据参数更新混合
UpdateBlendTreeParameters();
}
}
public void SetBool(string name, bool value)
{
animationParameters.SetBool(name, value);
}
public void SetInt(string name, int value)
{
animationParameters.SetInt(name, value);
}
/// <summary>
/// 设置混合参数
/// </summary>
public void SetBlendParameters(Vector2 parameters)
{
blendParameters = parameters;
if (currentBlendTree != null)
{
// 更新混合树
UpdateBlendTree();
}
}
/// <summary>
/// 更新混合树
/// </summary>
private void UpdateBlendTree()
{
if (currentBlendTree == null)
return;
// 获取混合后的动画剪辑
var blendedClip = currentBlendTree.GetBlendedClip(blendParameters);
if (blendedClip != null)
{
// 创建临时状态用于混合树
AnimationState blendState = new AnimationState
{
StateName = "BlendTree",
AnimationClip = blendedClip,
Speed = 1f,
Loop = true
};
// 设置到基础层
if (animationLayers.Count > 0)
{
animationLayers[0].CurrentState = blendState;
}
}
}
/// <summary>
/// 应用根运动
/// </summary>
private void ApplyRootMotion()
{
if (!updateRootMotion || currentState == null || !currentState.HasRootMotion)
return;
// 计算根运动位移
Vector2 motionDelta = currentState.RootMotionDelta * Time.deltaTime;
// 应用到Transform
transform.Translate(motionDelta, Space.World);
}
/// <summary>
/// 添加动画图层
/// </summary>
public void AddAnimationLayer(string layerName, float weight = 1f, AvatarMask mask = null)
{
AnimationLayer newLayer = new AnimationLayer
{
LayerName = layerName,
Weight = weight,
Mask = mask
};
animationLayers.Add(newLayer);
}
/// <summary>
/// 移除动画图层
/// </summary>
public void RemoveAnimationLayer(string layerName)
{
for (int i = animationLayers.Count - 1; i >= 0; i--)
{
if (animationLayers[i].LayerName == layerName)
{
animationLayers.RemoveAt(i);
break;
}
}
}
/// <summary>
/// 设置图层权重
/// </summary>
public void SetLayerWeight(string layerName, float weight)
{
foreach (var layer in animationLayers)
{
if (layer.LayerName == layerName)
{
layer.Weight = Mathf.Clamp01(weight);
break;
}
}
}
/// <summary>
/// 获取当前状态信息
/// </summary>
public StateInfo GetCurrentStateInfo()
{
return new StateInfo
{
StateName = currentState?.StateName,
IsTransitioning = stateTransitionTime < stateTransitionDuration,
TransitionProgress = stateTransitionTime / stateTransitionDuration
};
}
/// <summary>
/// 状态信息结构
/// </summary>
public struct StateInfo
{
public string StateName;
public bool IsTransitioning;
public float TransitionProgress;
}
}
}
7.3 Tile地图的数学算法与高级编辑
7.3.1 Tile的创建与自动拼接算法
Tile地图系统的核心是自动拼接算法(Auto-Tiling)。商业级2D游戏需要复杂的拼接规则和高效的生成算法。
自动拼接算法原理:
- 位掩码算法:使用4位或8位表示相邻Tile
- Marching Squares:用于生成平滑边界
- Wang Tiles:使用预先设计的Tile集合
以下是一个高级Tile系统的实现:
using UnityEngine;
using UnityEngine.Tilemaps;
using System.Collections.Generic;
namespace CommercialProject.TilemapSystem
{
/// <summary>
/// 高级Tile系统
/// 支持自动拼接、动态生成和优化
/// </summary>
public class AdvancedTileSystem : MonoBehaviour
{
[SerializeField]
private Tilemap targetTilemap;
[SerializeField]
private AdvancedRuleTile ruleTile;
[SerializeField]
private bool enableAutoUpdate = true;
[SerializeField]
private OptimizationLevel optimizationLevel = OptimizationLevel.Medium;
// Tile数据缓存
private Dictionary<Vector3Int, TileData> tileDataCache;
private Dictionary<Vector3Int, TileNeighbors> neighborCache;
/// <summary>
/// Tile数据
/// </summary>
public class TileData
{
public Vector3Int Position;
public TileBase Tile;
public int TileId;
public int VariantIndex;
public TileFlags Flags;
public Color Color;
}
/// <summary>
/// 邻居信息
/// </summary>
public struct TileNeighbors
{
public TileBase Up;
public TileBase Down;
public TileBase Left;
public TileBase Right;
public TileBase UpLeft;
public TileBase UpRight;
public TileBase DownLeft;
public TileBase DownRight;
/// <summary>
/// 计算位掩码
/// </summary>
public int CalculateBitmask(TileBase self)
{
int mask = 0;
// 4位方向
if (Up == self) mask |= 1 << 0; // 上
if (Right == self) mask |= 1 << 1; // 右
if (Down == self) mask |= 1 << 2; // 下
if (Left == self) mask |= 1 << 3; // 左
// 8位方向(如果需要)
// if (UpLeft == self) mask |= 1 << 4;
// if (UpRight == self) mask |= 1 << 5;
// if (DownLeft == self) mask |= 1 << 6;
// if (DownRight == self) mask |= 1 << 7;
return mask;
}
}
/// <summary>
/// 优化级别
/// </summary>
public enum OptimizationLevel
{
None,
Low,
Medium,
High
}
/// <summary>
/// 高级规则Tile
/// </summary>
[CreateAssetMenu(fileName = "AdvancedRuleTile", menuName = "2D/Tiles/Advanced Rule Tile")]
public class AdvancedRuleTile : RuleTile
{
// 扩展规则Tile以支持更多功能
[System.Serializable]
public class ExtendedRule : RuleTile.TilingRule
{
public int Priority = 0;
public bool UseRandomVariant = false;
public TileBase[] RandomVariants;
public AnimationCurve VariantDistribution;
}
public ExtendedRule[] extendedRules;
public override bool RuleMatch(int neighbor, TileBase other)
{
// 扩展匹配规则
if (neighbor == RuleTile.TilingRule.Neighbor.NotEmpty && other != null)
return true;
if (neighbor == RuleTile.TilingRule.Neighbor.Empty && other == null)
return true;
return base.RuleMatch(neighbor, other);
}
/// <summary>
/// 获取匹配的规则
/// </summary>
public ExtendedRule GetMatchingRule(TileNeighbors neighbors, TileBase self)
{
if (extendedRules == null)
return null;
// 按优先级排序
List<ExtendedRule> sortedRules = new List<ExtendedRule>(extendedRules);
sortedRules.Sort((a, b) => b.Priority.CompareTo(a.Priority));
foreach (var rule in sortedRules)
{
if (CheckRuleMatch(rule, neighbors, self))
{
return rule;
}
}
return null;
}
/// <summary>
/// 检查规则匹配
/// </summary>
private bool CheckRuleMatch(ExtendedRule rule, TileNeighbors neighbors, TileBase self)
{
// 检查每个方向的匹配
if (!CheckDirectionMatch(rule.m_Neighbors[0], neighbors.Up, self))
return false;
if (!CheckDirectionMatch(rule.m_Neighbors[1], neighbors.Right, self))
return false;
if (!CheckDirectionMatch(rule.m_Neighbors[2], neighbors.Down, self))
return false;
if (!CheckDirectionMatch(rule.m_Neighbors[3], neighbors.Left, self))
return false;
return true;
}
private bool CheckDirectionMatch(int ruleNeighbor, TileBase actualTile, TileBase self)
{
if (ruleNeighbor == TilingRule.Neighbor.This)
return actualTile == self;
if (ruleNeighbor == TilingRule.Neighbor.NotThis)
return actualTile != self && actualTile != null;
if (ruleNeighbor == TilingRule.Neighbor.Any)
return true;
if (ruleNeighbor == TilingRule.Neighbor.Empty)
return actualTile == null;
return base.RuleMatch(ruleNeighbor, actualTile);
}
}
private void Awake()
{
InitializeTileSystem();
}
private void Start()
{
BuildTileCache();
if (enableAutoUpdate)
{
StartCoroutine(AutoUpdateTiles());
}
}
/// <summary>
/// 初始化Tile系统
/// </summary>
private void InitializeTileSystem()
{
if (targetTilemap == null)
{
targetTilemap = GetComponent<Tilemap>();
}
tileDataCache = new Dictionary<Vector3Int, TileData>();
neighborCache = new Dictionary<Vector3Int, TileNeighbors>();
}
/// <summary>
/// 构建Tile缓存
/// </summary>
private void BuildTileCache()
{
tileDataCache.Clear();
neighborCache.Clear();
// 获取Tilemap边界
targetTilemap.CompressBounds();
BoundsInt bounds = targetTilemap.cellBounds;
// 遍历所有Tile
for (int x = bounds.xMin; x < bounds.xMax; x++)
{
for (int y = bounds.yMin; y < bounds.yMax; y++)
{
Vector3Int position = new Vector3Int(x, y, 0);
TileBase tile = targetTilemap.GetTile(position);
if (tile != null)
{
// 存储Tile数据
TileData data = new TileData
{
Position = position,
Tile = tile,
TileId = tile.GetInstanceID(),
Color = targetTilemap.GetColor(position)
};
tileDataCache[position] = data;
// 计算并存储邻居信息
TileNeighbors neighbors = CalculateNeighbors(position);
neighborCache[position] = neighbors;
}
}
}
}
/// <summary>
/// 计算邻居信息
/// </summary>
private TileNeighbors CalculateNeighbors(Vector3Int position)
{
TileNeighbors neighbors = new TileNeighbors();
// 4方向邻居
neighbors.Up = targetTilemap.GetTile(position + Vector3Int.up);
neighbors.Down = targetTilemap.GetTile(position + Vector3Int.down);
neighbors.Left = targetTilemap.GetTile(position + Vector3Int.left);
neighbors.Right = targetTilemap.GetTile(position + Vector3Int.right);
// 8方向邻居(对角线)
neighbors.UpLeft = targetTilemap.GetTile(position + Vector3Int.up + Vector3Int.left);
neighbors.UpRight = targetTilemap.GetTile(position + Vector3Int.up + Vector3Int.right);
neighbors.DownLeft = targetTilemap.GetTile(position + Vector3Int.down + Vector3Int.left);
neighbors.DownRight = targetTilemap.GetTile(position + Vector3Int.down + Vector3Int.right);
return neighbors;
}
/// <summary>
/// 自动更新Tile协程
/// </summary>
private System.Collections.IEnumerator AutoUpdateTiles()
{
while (enableAutoUpdate)
{
yield return new WaitForSeconds(0.1f);
// 检查需要更新的Tile
UpdateDirtyTiles();
}
}
/// <summary>
/// 更新需要刷新的Tile
/// </summary>
private void UpdateDirtyTiles()
{
List<Vector3Int> dirtyPositions = FindDirtyTiles();
foreach (Vector3Int position in dirtyPositions)
{
UpdateTileAtPosition(position);
}
}
/// <summary>
/// 查找需要更新的Tile
/// </summary>
private List<Vector3Int> FindDirtyTiles()
{
List<Vector3Int> dirtyPositions = new List<Vector3Int>();
// 检查所有缓存的Tile
foreach (var kvp in tileDataCache)
{
Vector3Int position = kvp.Key;
TileData data = kvp.Value;
// 获取当前Tile
TileBase currentTile = targetTilemap.GetTile(position);
// 如果Tile发生变化
if (currentTile != data.Tile)
{
dirtyPositions.Add(position);
}
}
return dirtyPositions;
}
/// <summary>
/// 更新指定位置的Tile
/// </summary>
public void UpdateTileAtPosition(Vector3Int position)
{
if (ruleTile == null)
return;
// 获取当前Tile
TileBase currentTile = targetTilemap.GetTile(position);
// 获取邻居信息
TileNeighbors neighbors = CalculateNeighbors(position);
// 应用规则Tile
if (ruleTile is AdvancedRuleTile advancedRuleTile)
{
var matchingRule = advancedRuleTile.GetMatchingRule(neighbors, currentTile);
if (matchingRule != null)
{
// 应用规则
ApplyRuleTile(position, matchingRule, neighbors);
}
}
else
{
// 使用基础RuleTile
ruleTile.RefreshTile(position, targetTilemap);
}
// 更新缓存
UpdateTileCache(position);
// 更新邻居Tile(如果需要)
UpdateNeighborTiles(position);
}
/// <summary>
/// 应用规则Tile
/// </summary>
private void ApplyRuleTile(Vector3Int position, AdvancedRuleTile.ExtendedRule rule, TileNeighbors neighbors)
{
TileBase tileToSet = rule.m_Sprites.Length > 0 ? ruleTile : null;
if (tileToSet != null)
{
// 设置Tile
targetTilemap.SetTile(position, tileToSet);
// 设置变换矩阵
Matrix4x4 transformMatrix = Matrix4x4.TRS(
rule.m_PerlinScale > 0 ? GetPerlinOffset(position, rule) : Vector3.zero,
Quaternion.Euler(0, 0, rule.m_Rotation),
Vector3.one
);
targetTilemap.SetTransformMatrix(position, transformMatrix);
// 设置颜色
if (rule.m_RandomColor)
{
Color randomColor = new Color(
Random.value,
Random.value,
Random.value,
1f
);
targetTilemap.SetColor(position, randomColor);
}
// 处理随机变体
if (rule.UseRandomVariant && rule.RandomVariants != null && rule.RandomVariants.Length > 0)
{
int variantIndex = SelectRandomVariant(rule);
if (variantIndex >= 0 && variantIndex < rule.RandomVariants.Length)
{
targetTilemap.SetTile(position, rule.RandomVariants[variantIndex]);
}
}
}
}
/// <summary>
/// 获取柏林噪声偏移
/// </summary>
private Vector3 GetPerlinOffset(Vector3Int position, AdvancedRuleTile.ExtendedRule rule)
{
float perlinX = Mathf.PerlinNoise(position.x * rule.m_PerlinScale, position.y * rule.m_PerlinScale);
float perlinY = Mathf.PerlinNoise((position.x + 100) * rule.m_PerlinScale, (position.y + 100) * rule.m_PerlinScale);
return new Vector3(
(perlinX - 0.5f) * rule.m_PerlinScale,
(perlinY - 0.5f) * rule.m_PerlinScale,
0
);
}
/// <summary>
/// 选择随机变体
/// </summary>
private int SelectRandomVariant(AdvancedRuleTile.ExtendedRule rule)
{
if (rule.VariantDistribution != null && rule.VariantDistribution.keys.Length > 0)
{
// 使用分布曲线
float randomValue = Random.value;
float curveValue = rule.VariantDistribution.Evaluate(randomValue);
return Mathf.FloorToInt(curveValue * rule.RandomVariants.Length);
}
else
{
// 均匀随机
return Random.Range(0, rule.RandomVariants.Length);
}
}
/// <summary>
/// 更新Tile缓存
/// </summary>
private void UpdateTileCache(Vector3Int position)
{
TileBase tile = targetTilemap.GetTile(position);
if (tile != null)
{
TileData data = new TileData
{
Position = position,
Tile = tile,
TileId = tile.GetInstanceID(),
Color = targetTilemap.GetColor(position)
};
tileDataCache[position] = data;
// 更新邻居信息
TileNeighbors neighbors = CalculateNeighbors(position);
neighborCache[position] = neighbors;
}
else
{
// 移除缓存
tileDataCache.Remove(position);
neighborCache.Remove(position);
}
}
/// <summary>
/// 更新邻居Tile
/// </summary>
private void UpdateNeighborTiles(Vector3Int centerPosition)
{
// 更新直接邻居
Vector3Int[] neighborOffsets = new Vector3Int[]
{
Vector3Int.up,
Vector3Int.down,
Vector3Int.left,
Vector3Int.right,
Vector3Int.up + Vector3Int.left,
Vector3Int.up + Vector3Int.right,
Vector3Int.down + Vector3Int.left,
Vector3Int.down + Vector3Int.right
};
foreach (Vector3Int offset in neighborOffsets)
{
Vector3Int neighborPos = centerPosition + offset;
UpdateTileAtPosition(neighborPos);
}
}
/// <summary>
/// 批量设置Tile
/// </summary>
public void SetTiles(Vector3Int[] positions, TileBase tile)
{
targetTilemap.SetTiles(positions, new TileBase[positions.Length]);
// 更新缓存
foreach (Vector3Int position in positions)
{
UpdateTileCache(position);
UpdateNeighborTiles(position);
}
}
/// <summary>
/// 填充区域
/// </summary>
public void FillArea(Vector3Int start, Vector3Int end, TileBase tile)
{
List<Vector3Int> positions = new List<Vector3Int>();
int minX = Mathf.Min(start.x, end.x);
int maxX = Mathf.Max(start.x, end.x);
int minY = Mathf.Min(start.y, end.y);
int maxY = Mathf.Max(start.y, end.y);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
positions.Add(new Vector3Int(x, y, 0));
}
}
SetTiles(positions.ToArray(), tile);
}
/// <summary>
/// 优化Tilemap
/// </summary>
public void OptimizeTilemap()
{
switch (optimizationLevel)
{
case OptimizationLevel.Low:
OptimizeLow();
break;
case OptimizationLevel.Medium:
OptimizeMedium();
break;
case OptimizationLevel.High:
OptimizeHigh();
break;
}
}
/// <summary>
/// 低级别优化
/// </summary>
private void OptimizeLow()
{
// 压缩边界
targetTilemap.CompressBounds();
}
/// <summary>
/// 中级别优化
/// </summary>
private void OptimizeMedium()
{
OptimizeLow();
// 合并相同Tile的连续块
MergeTileChunks();
}
/// <summary>
/// 高级别优化
/// </summary>
private void OptimizeHigh()
{
OptimizeMedium();
// 移除不可见Tile
RemoveInvisibleTiles();
// 优化碰撞体
OptimizeColliders();
}
/// <summary>
/// 合并Tile块
/// </summary>
private void MergeTileChunks()
{
// 检测连续的相同Tile并合并
// 实际实现需要更复杂的算法
}
/// <summary>
/// 获取Tile信息
/// </summary>
public TileData GetTileData(Vector3Int position)
{
if (tileDataCache.ContainsKey(position))
{
return tileDataCache[position];
}
return null;
}
/// <summary>
/// 获取区域内的所有Tile
/// </summary>
public List<TileData> GetTilesInArea(BoundsInt area)
{
List<TileData> tiles = new List<TileData>();
for (int x = area.xMin; x < area.xMax; x++)
{
for (int y = area.yMin; y < area.yMax; y++)
{
Vector3Int position = new Vector3Int(x, y, 0);
TileData data = GetTileData(position);
if (data != null)
{
tiles.Add(data);
}
}
}
return tiles;
}
}
}
7.4 2D碰撞检测的数学原理与优化
7.4.1 Collider 2D的几何形状与相交检测
2D碰撞检测的核心是几何形状的相交检测。不同的碰撞体形状需要不同的数学算法。
常见形状相交检测算法:
- AABB(Axis-Aligned Bounding Box):矩形相交检测
- 圆形相交:距离检测
distance <= r1 + r2 - 凸多边形相交:分离轴定理(SAT)
- 线段相交:参数方程求解
以下是一个高级2D碰撞检测系统的实现:
using UnityEngine;
using System.Collections.Generic;
namespace CommercialProject.Physics2D
{
/// <summary>
/// 高级2D碰撞检测系统
/// 支持多种几何形状和优化算法
/// </summary>
public class AdvancedCollisionSystem2D : MonoBehaviour
{
[SerializeField]
private bool enableBroadPhase = true;
[SerializeField]
private BroadPhaseMethod broadPhaseMethod = BroadPhaseMethod.SpatialHash;
[SerializeField]
private float gridCellSize = 1f;
// 碰撞体注册
private List<Collider2D> registeredColliders;
private Dictionary<Collider2D, ColliderData> colliderData;
// 空间分区
private SpatialHashGrid spatialGrid;
private QuadTree quadTree;
/// <summary>
/// 碰撞体数据
/// </summary>
public class ColliderData
{
public Collider2D Collider;
public Bounds WorldBounds;
public Vector2 Velocity;
public int LayerMask;
public bool IsTrigger;
public object UserData;
}
/// <summary>
/// 碰撞结果
/// </summary>
public struct CollisionResult
{
public bool HasCollision;
public Vector2 Point;
public Vector2 Normal;
public float Depth;
public Collider2D OtherCollider;
public Rigidbody2D OtherRigidbody;
}
/// <summary>
/// 碰撞信息
/// </summary>
public struct CollisionInfo
{
public Collider2D Collider;
public Collider2D OtherCollider;
public Vector2 ContactPoint;
public Vector2 RelativeVelocity;
public float Impulse;
}
/// <summary>
/// 宽相位方法
/// </summary>
public enum BroadPhaseMethod
{
None,
SpatialHash,
QuadTree,
SweepAndPrune
}
private void Awake()
{
InitializeCollisionSystem();
}
private void FixedUpdate()
{
UpdateCollisionSystem();
}
/// <summary>
/// 初始化碰撞系统
/// </summary>
private void InitializeCollisionSystem()
{
registeredColliders = new List<Collider2D>();
colliderData = new Dictionary<Collider2D, ColliderData>();
if (enableBroadPhase)
{
InitializeBroadPhase();
}
}
/// <summary>
/// 初始化宽相位
/// </summary>
private void InitializeBroadPhase()
{
switch (broadPhaseMethod)
{
case BroadPhaseMethod.SpatialHash:
spatialGrid = new SpatialHashGrid(gridCellSize);
break;
case BroadPhaseMethod.QuadTree:
quadTree = new QuadTree(new Rect(-100, -100, 200, 200), 4);
break;
}
}
/// <summary>
/// 更新碰撞系统
/// </summary>
private void UpdateCollisionSystem()
{
// 更新所有碰撞体数据
UpdateColliderData();
// 执行宽相位检测
List<ColliderPair> potentialPairs = PerformBroadPhase();
// 执行窄相位检测
PerformNarrowPhase(potentialPairs);
// 更新空间分区(如果需要)
UpdateSpatialPartition();
}
/// <summary>
/// 更新碰撞体数据
/// </summary>
private void UpdateColliderData()
{
foreach (var collider in registeredColliders)
{
if (collider != null)
{
UpdateColliderBounds(collider);
// 更新速度信息
Rigidbody2D rb = collider.attachedRigidbody;
if (rb != null)
{
colliderData[collider].Velocity = rb.velocity;
}
}
}
}
/// <summary>
/// 更新碰撞体边界
/// </summary>
private void UpdateColliderBounds(Collider2D collider)
{
if (colliderData.ContainsKey(collider))
{
colliderData[collider].WorldBounds = collider.bounds;
}
}
/// <summary>
/// 执行宽相位检测
/// </summary>
private List<ColliderPair> PerformBroadPhase()
{
if (!enableBroadPhase)
{
// 没有宽相位,检查所有可能的组合
return GetAllPossiblePairs();
}
switch (broadPhaseMethod)
{
case BroadPhaseMethod.SpatialHash:
return spatialGrid.GetPotentialPairs();
case BroadPhaseMethod.QuadTree:
return quadTree.GetPotentialPairs();
case BroadPhaseMethod.SweepAndPrune:
return PerformSweepAndPrune();
default:
return new List<ColliderPair>();
}
}
/// <summary>
/// 获取所有可能碰撞对
/// </summary>
private List<ColliderPair> GetAllPossiblePairs()
{
List<ColliderPair> pairs = new List<ColliderPair>();
for (int i = 0; i < registeredColliders.Count; i++)
{
for (int j = i + 1; j < registeredColliders.Count; j++)
{
pairs.Add(new ColliderPair(registeredColliders[i], registeredColliders[j]));
}
}
return pairs;
}
/// <summary>
/// 执行窄相位检测
/// </summary>
private void PerformNarrowPhase(List<ColliderPair> potentialPairs)
{
foreach (var pair in potentialPairs)
{
if (ShouldCheckCollision(pair.A, pair.B))
{
CollisionResult result = CheckCollision(pair.A, pair.B);
if (result.HasCollision)
{
HandleCollision(pair.A, pair.B, result);
}
}
}
}
/// <summary>
/// 检查是否应该检测碰撞
/// </summary>
private bool ShouldCheckCollision(Collider2D a, Collider2D b)
{
if (a == null || b == null)
return false;
// 检查层级
if (!Physics2D.GetIgnoreLayerCollision(a.gameObject.layer, b.gameObject.layer))
return false;
// 检查是否为触发器
if (a.isTrigger || b.isTrigger)
{
// 触发器需要特殊处理
return true;
}
// 检查边界框相交(快速拒绝)
Bounds boundsA = colliderData[a].WorldBounds;
Bounds boundsB = colliderData[b].WorldBounds;
return boundsA.Intersects(boundsB);
}
/// <summary>
/// 检查碰撞
/// </summary>
public CollisionResult CheckCollision(Collider2D a, Collider2D b)
{
CollisionResult result = new CollisionResult
{
HasCollision = false
};
// 根据碰撞体类型选择不同的检测算法
if (a is BoxCollider2D && b is BoxCollider2D)
{
result = CheckBoxBoxCollision((BoxCollider2D)a, (BoxCollider2D)b);
}
else if (a is CircleCollider2D && b is CircleCollider2D)
{
result = CheckCircleCircleCollision((CircleCollider2D)a, (CircleCollider2D)b);
}
else if (a is PolygonCollider2D && b is PolygonCollider2D)
{
result = CheckPolygonPolygonCollision((PolygonCollider2D)a, (PolygonCollider2D)b);
}
else if (a is BoxCollider2D && b is CircleCollider2D)
{
result = CheckBoxCircleCollision((BoxCollider2D)a, (CircleCollider2D)b);
}
else if (a is CircleCollider2D && b is BoxCollider2D)
{
result = CheckBoxCircleCollision((BoxCollider2D)b, (CircleCollider2D)a);
// 反转法线
if (result.HasCollision)
{
result.Normal = -result.Normal;
}
}
return result;
}
/// <summary>
/// 检查盒子与盒子碰撞
/// </summary>
private CollisionResult CheckBoxBoxCollision(BoxCollider2D a, BoxCollider2D b)
{
CollisionResult result = new CollisionResult();
// 获取世界空间的边界
Bounds boundsA = a.bounds;
Bounds boundsB = b.bounds;
// 检查是否相交
if (!boundsA.Intersects(boundsB))
{
result.HasCollision = false;
return result;
}
// 计算相交区域
float overlapX = Mathf.Min(
boundsA.max.x - boundsB.min.x,
boundsB.max.x - boundsA.min.x
);
float overlapY = Mathf.Min(
boundsA.max.y - boundsB.min.y,
boundsB.max.y - boundsA.min.y
);
// 选择最小的重叠方向
if (overlapX < overlapY)
{
result.Depth = overlapX;
result.Normal = boundsA.center.x < boundsB.center.x ?
Vector2.right : Vector2.left;
}
else
{
result.Depth = overlapY;
result.Normal = boundsA.center.y < boundsB.center.y ?
Vector2.up : Vector2.down;
}
// 计算接触点(简化:使用中心点)
result.Point = (boundsA.center + boundsB.center) * 0.5f;
result.HasCollision = true;
result.OtherCollider = b;
return result;
}
/// <summary>
/// 检查圆形与圆形碰撞
/// </summary>
private CollisionResult CheckCircleCircleCollision(CircleCollider2D a, CircleCollider2D b)
{
CollisionResult result = new CollisionResult();
Vector2 centerA = a.bounds.center;
Vector2 centerB = b.bounds.center;
float radiusA = a.radius * Mathf.Max(a.transform.lossyScale.x, a.transform.lossyScale.y);
float radiusB = b.radius * Mathf.Max(b.transform.lossyScale.x, b.transform.lossyScale.y);
Vector2 delta = centerB - centerA;
float distance = delta.magnitude;
float totalRadius = radiusA + radiusB;
if (distance < totalRadius)
{
result.HasCollision = true;
result.Depth = totalRadius - distance;
result.Normal = delta.normalized;
result.Point = centerA + result.Normal * radiusA;
result.OtherCollider = b;
}
else
{
result.HasCollision = false;
}
return result;
}
/// <summary>
/// 检查多边形与多边形碰撞(使用分离轴定理)
/// </summary>
private CollisionResult CheckPolygonPolygonCollision(PolygonCollider2D a, PolygonCollider2D b)
{
CollisionResult result = new CollisionResult();
// 获取顶点(世界坐标)
Vector2[] verticesA = GetWorldVertices(a);
Vector2[] verticesB = GetWorldVertices(b);
// 使用分离轴定理
float minOverlap = float.MaxValue;
Vector2 minAxis = Vector2.zero;
// 检查A的边
for (int i = 0; i < verticesA.Length; i++)
{
Vector2 edge = verticesA[(i + 1) % verticesA.Length] - verticesA[i];
Vector2 axis = new Vector2(-edge.y, edge.x).normalized;
float overlap = CalculateOverlapOnAxis(verticesA, verticesB, axis);
if (overlap <= 0)
{
// 分离轴存在,没有碰撞
result.HasCollision = false;
return result;
}
if (overlap < minOverlap)
{
minOverlap = overlap;
minAxis = axis;
}
}
// 检查B的边
for (int i = 0; i < verticesB.Length; i++)
{
Vector2 edge = verticesB[(i + 1) % verticesB.Length] - verticesB[i];
Vector2 axis = new Vector2(-edge.y, edge.x).normalized;
float overlap = CalculateOverlapOnAxis(verticesA, verticesB, axis);
if (overlap <= 0)
{
result.HasCollision = false;
return result;
}
if (overlap < minOverlap)
{
minOverlap = overlap;
minAxis = axis;
}
}
// 所有轴都有重叠,存在碰撞
result.HasCollision = true;
result.Depth = minOverlap;
result.Normal = minAxis;
// 确保法线指向A到B
Vector2 centerA = CalculateCentroid(verticesA);
Vector2 centerB = CalculateCentroid(verticesB);
if (Vector2.Dot(centerB - centerA, minAxis) < 0)
{
result.Normal = -minAxis;
}
// 计算接触点(简化)
result.Point = centerA;
result.OtherCollider = b;
return result;
}
/// <summary>
/// 检查盒子与圆形碰撞
/// </summary>
private CollisionResult CheckBoxCircleCollision(BoxCollider2D box, CircleCollider2D circle)
{
CollisionResult result = new CollisionResult();
// 将圆形中心转换到盒子局部空间
Vector2 circleCenter = circle.bounds.center;
Vector2 boxCenter = box.bounds.center;
Vector2 boxSize = box.size * 0.5f;
// 计算盒子局部坐标
Vector2 localCirclePos = circleCenter - boxCenter;
// 旋转处理(如果需要)
float boxRotation = box.transform.eulerAngles.z * Mathf.Deg2Rad;
if (Mathf.Abs(boxRotation) > 0.001f)
{
float cos = Mathf.Cos(-boxRotation);
float sin = Mathf.Sin(-boxRotation);
localCirclePos = new Vector2(
localCirclePos.x * cos - localCirclePos.y * sin,
localCirclePos.x * sin + localCirclePos.y * cos
);
}
// 找到最近的点
Vector2 closestPoint = new Vector2(
Mathf.Clamp(localCirclePos.x, -boxSize.x, boxSize.x),
Mathf.Clamp(localCirclePos.y, -boxSize.y, boxSize.y)
);
// 计算距离
Vector2 delta = localCirclePos - closestPoint;
float distance = delta.magnitude;
float circleRadius = circle.radius * Mathf.Max(
circle.transform.lossyScale.x,
circle.transform.lossyScale.y
);
if (distance < circleRadius)
{
result.HasCollision = true;
result.Depth = circleRadius - distance;
// 转换回世界空间
if (Mathf.Abs(boxRotation) > 0.001f)
{
float cos = Mathf.Cos(boxRotation);
float sin = Mathf.Sin(boxRotation);
delta = new Vector2(
delta.x * cos - delta.y * sin,
delta.x * sin + delta.y * cos
);
}
result.Normal = delta.normalized;
result.Point = boxCenter + closestPoint;
result.OtherCollider = circle;
}
else
{
result.HasCollision = false;
}
return result;
}
/// <summary>
/// 获取世界坐标顶点
/// </summary>
private Vector2[] GetWorldVertices(PolygonCollider2D collider)
{
Vector2[] localVertices = collider.points;
Vector2[] worldVertices = new Vector2[localVertices.Length];
Transform transform = collider.transform;
for (int i = 0; i < localVertices.Length; i++)
{
worldVertices[i] = transform.TransformPoint(localVertices[i]);
}
return worldVertices;
}
/// <summary>
/// 计算在轴上的重叠
/// </summary>
private float CalculateOverlapOnAxis(Vector2[] verticesA, Vector2[] verticesB, Vector2 axis)
{
float minA, maxA, minB, maxB;
ProjectVertices(verticesA, axis, out minA, out maxA);
ProjectVertices(verticesB, axis, out minB, out maxB);
if (minA > maxB || minB > maxA)
{
return 0; // 没有重叠
}
return Mathf.Min(maxA, maxB) - Mathf.Max(minA, minB);
}
/// <summary>
/// 投影顶点到轴
/// </summary>
private void ProjectVertices(Vector2[] vertices, Vector2 axis, out float min, out float max)
{
min = float.MaxValue;
max = float.MinValue;
foreach (Vector2 vertex in vertices)
{
float projection = Vector2.Dot(vertex, axis);
min = Mathf.Min(min, projection);
max = Mathf.Max(max, projection);
}
}
/// <summary>
/// 计算多边形质心
/// </summary>
private Vector2 CalculateCentroid(Vector2[] vertices)
{
Vector2 centroid = Vector2.zero;
float area = 0f;
for (int i = 0; i < vertices.Length; i++)
{
Vector2 current = vertices[i];
Vector2 next = vertices[(i + 1) % vertices.Length];
float cross = current.x * next.y - next.x * current.y;
area += cross;
centroid.x += (current.x + next.x) * cross;
centroid.y += (current.y + next.y) * cross;
}
area *= 0.5f;
centroid /= (6f * area);
return centroid;
}
/// <summary>
/// 处理碰撞
/// </summary>
private void HandleCollision(Collider2D a, Collider2D b, CollisionResult result)
{
// 发送碰撞消息
SendCollisionMessages(a, b, result);
// 物理响应
ApplyCollisionResponse(a, b, result);
// 触发器处理
if (a.isTrigger || b.isTrigger)
{
HandleTriggerCollision(a, b, result);
}
}
/// <summary>
/// 发送碰撞消息
/// </summary>
private void SendCollisionMessages(Collider2D a, Collider2D b, CollisionResult result)
{
// 发送给碰撞体A
a.SendMessage("OnCustomCollisionEnter2D",
new CollisionInfo
{
Collider = a,
OtherCollider = b,
ContactPoint = result.Point,
RelativeVelocity = CalculateRelativeVelocity(a, b),
Impulse = CalculateCollisionImpulse(a, b, result)
},
SendMessageOptions.DontRequireReceiver);
// 发送给碰撞体B
b.SendMessage("OnCustomCollisionEnter2D",
new CollisionInfo
{
Collider = b,
OtherCollider = a,
ContactPoint = result.Point,
RelativeVelocity = CalculateRelativeVelocity(b, a),
Impulse = CalculateCollisionImpulse(b, a, result)
},
SendMessageOptions.DontRequireReceiver);
}
/// <summary>
/// 应用碰撞响应
/// </summary>
private void ApplyCollisionResponse(Collider2D a, Collider2D b, CollisionResult result)
{
Rigidbody2D rbA = a.attachedRigidbody;
Rigidbody2D rbB = b.attachedRigidbody;
// 如果两个都有刚体,应用物理响应
if (rbA != null && rbB != null && !a.isTrigger && !b.isTrigger)
{
// 计算冲量
Vector2 impulse = CalculateImpulse(rbA, rbB, result);
// 应用冲量
rbA.AddForceAtPosition(-impulse, result.Point, ForceMode2D.Impulse);
rbB.AddForceAtPosition(impulse, result.Point, ForceMode2D.Impulse);
// 处理摩擦
ApplyFriction(rbA, rbB, result);
}
else if (rbA != null && !a.isTrigger)
{
// 只有A有刚体,推开A
Vector2 push = result.Normal * result.Depth * 0.5f;
rbA.MovePosition(rbA.position + push);
}
else if (rbB != null && !b.isTrigger)
{
// 只有B有刚体,推开B
Vector2 push = -result.Normal * result.Depth * 0.5f;
rbB.MovePosition(rbB.position + push);
}
}
/// <summary>
/// 计算相对速度
/// </summary>
private Vector2 CalculateRelativeVelocity(Collider2D a, Collider2D b)
{
Rigidbody2D rbA = a.attachedRigidbody;
Rigidbody2D rbB = b.attachedRigidbody;
Vector2 velA = rbA != null ? rbA.velocity : Vector2.zero;
Vector2 velB = rbB != null ? rbB.velocity : Vector2.zero;
return velB - velA;
}
/// <summary>
/// 计算碰撞冲量
/// </summary>
private float CalculateCollisionImpulse(Collider2D a, Collider2D b, CollisionResult result)
{
// 简化计算
Rigidbody2D rbA = a.attachedRigidbody;
Rigidbody2D rbB = b.attachedRigidbody;
if (rbA == null || rbB == null)
return 0f;
Vector2 relativeVel = CalculateRelativeVelocity(a, b);
float normalVel = Vector2.Dot(relativeVel, result.Normal);
// 如果物体正在分离,冲量为0
if (normalVel > 0)
return 0f;
float restitution = Mathf.Min(rbA.sharedMaterial?.bounciness ?? 0,
rbB.sharedMaterial?.bounciness ?? 0);
float impulseMagnitude = -(1 + restitution) * normalVel;
// 考虑质量
float invMassA = rbA.mass > 0 ? 1f / rbA.mass : 0f;
float invMassB = rbB.mass > 0 ? 1f / rbB.mass : 0f;
impulseMagnitude /= (invMassA + invMassB);
return impulseMagnitude;
}
/// <summary>
/// 计算冲量
/// </summary>
private Vector2 CalculateImpulse(Rigidbody2D rbA, Rigidbody2D rbB, CollisionResult result)
{
float impulseMagnitude = CalculateCollisionImpulse(rbA.GetComponent<Collider2D>(),
rbB.GetComponent<Collider2D>(),
result);
return result.Normal * impulseMagnitude;
}
/// <summary>
/// 应用摩擦力
/// </summary>
private void ApplyFriction(Rigidbody2D rbA, Rigidbody2D rbB, CollisionResult result)
{
// 计算切向速度
Vector2 relativeVel = CalculateRelativeVelocity(rbA.GetComponent<Collider2D>(),
rbB.GetComponent<Collider2D>());
Vector2 tangent = relativeVel - result.Normal * Vector2.Dot(relativeVel, result.Normal);
tangent.Normalize();
// 计算切向冲量
float tangentVel = Vector2.Dot(relativeVel, tangent);
float friction = Mathf.Min(rbA.sharedMaterial?.friction ?? 0,
rbB.sharedMaterial?.friction ?? 0);
float frictionImpulse = -tangentVel * friction;
// 应用摩擦力冲量
Vector2 frictionForce = tangent * frictionImpulse;
rbA.AddForceAtPosition(-frictionForce, result.Point, ForceMode2D.Impulse);
rbB.AddForceAtPosition(frictionForce, result.Point, ForceMode2D.Impulse);
}
/// <summary>
/// 处理触发器碰撞
/// </summary>
private void HandleTriggerCollision(Collider2D a, Collider2D b, CollisionResult result)
{
// 发送触发器消息
if (a.isTrigger)
{
a.SendMessage("OnCustomTriggerEnter2D", b, SendMessageOptions.DontRequireReceiver);
}
if (b.isTrigger)
{
b.SendMessage("OnCustomTriggerEnter2D", a, SendMessageOptions.DontRequireReceiver);
}
}
/// <summary>
/// 注册碰撞体
/// </summary>
public void RegisterCollider(Collider2D collider, object userData = null)
{
if (!registeredColliders.Contains(collider))
{
registeredColliders.Add(collider);
ColliderData data = new ColliderData
{
Collider = collider,
WorldBounds = collider.bounds,
LayerMask = 1 << collider.gameObject.layer,
IsTrigger = collider.isTrigger,
UserData = userData
};
colliderData[collider] = data;
// 添加到宽相位系统
if (enableBroadPhase)
{
AddToBroadPhase(collider, data.WorldBounds);
}
}
}
/// <summary>
/// 注销碰撞体
/// </summary>
public void UnregisterCollider(Collider2D collider)
{
if (registeredColliders.Contains(collider))
{
registeredColliders.Remove(collider);
colliderData.Remove(collider);
// 从宽相位系统移除
if (enableBroadPhase)
{
RemoveFromBroadPhase(collider);
}
}
}
/// <summary>
/// 添加到宽相位
/// </summary>
private void AddToBroadPhase(Collider2D collider, Bounds bounds)
{
switch (broadPhaseMethod)
{
case BroadPhaseMethod.SpatialHash:
spatialGrid.Add(collider, bounds);
break;
case BroadPhaseMethod.QuadTree:
quadTree.Insert(collider, bounds);
break;
}
}
/// <summary>
/// 从宽相位移除
/// </summary>
private void RemoveFromBroadPhase(Collider2D collider)
{
switch (broadPhaseMethod)
{
case BroadPhaseMethod.SpatialHash:
spatialGrid.Remove(collider);
break;
case BroadPhaseMethod.QuadTree:
quadTree.Remove(collider);
break;
}
}
/// <summary>
/// 更新空间分区
/// </summary>
private void UpdateSpatialPartition()
{
if (!enableBroadPhase)
return;
switch (broadPhaseMethod)
{
case BroadPhaseMethod.SpatialHash:
spatialGrid.Clear();
foreach (var collider in registeredColliders)
{
if (collider != null)
{
spatialGrid.Add(collider, collider.bounds);
}
}
break;
case BroadPhaseMethod.QuadTree:
quadTree.Clear();
foreach (var collider in registeredColliders)
{
if (collider != null)
{
quadTree.Insert(collider, collider.bounds);
}
}
break;
}
}
/// <summary>
/// 执行扫描和裁剪算法
/// </summary>
private List<ColliderPair> PerformSweepAndPrune()
{
List<ColliderPair> pairs = new List<ColliderPair>();
// 按X轴排序
registeredColliders.Sort((a, b) =>
{
float minXA = colliderData[a].WorldBounds.min.x;
float minXB = colliderData[b].WorldBounds.min.x;
return minXA.CompareTo(minXB);
});
// 扫描活动列表
List<Collider2D> activeList = new List<Collider2D>();
foreach (Collider2D collider in registeredColliders)
{
ColliderData data = colliderData[collider];
float currentMinX = data.WorldBounds.min.x;
// 移除不再重叠的碰撞体
for (int i = activeList.Count - 1; i >= 0; i--)
{
Collider2D active = activeList[i];
if (colliderData[active].WorldBounds.max.x < currentMinX)
{
activeList.RemoveAt(i);
}
}
// 检查与活动列表中所有碰撞体的重叠
foreach (Collider2D active in activeList)
{
// 检查Y轴重叠
Bounds boundsA = data.WorldBounds;
Bounds boundsB = colliderData[active].WorldBounds;
if (boundsA.min.y <= boundsB.max.y && boundsA.max.y >= boundsB.min.y)
{
pairs.Add(new ColliderPair(collider, active));
}
}
// 添加到活动列表
activeList.Add(collider);
}
return pairs;
}
/// <summary>
/// 射线检测
/// </summary>
public RaycastResult2D Raycast(Vector2 origin, Vector2 direction, float distance, int layerMask)
{
RaycastResult2D result = new RaycastResult2D();
float closestDistance = distance;
foreach (var collider in registeredColliders)
{
if (collider == null)
continue;
// 检查层级
if ((layerMask & (1 << collider.gameObject.layer)) == 0)
continue;
// 快速边界框检查
Bounds bounds = colliderData[collider].WorldBounds;
if (!bounds.IntersectRay(new Ray(origin, direction)))
continue;
// 精确检测(简化)
float hitDistance = CheckRayCollider(origin, direction, collider);
if (hitDistance > 0 && hitDistance < closestDistance)
{
closestDistance = hitDistance;
result.Hit = true;
result.Distance = hitDistance;
result.Point = origin + direction * hitDistance;
result.Normal = CalculateRaycastNormal(origin, direction, collider, hitDistance);
result.Collider = collider;
}
}
return result;
}
/// <summary>
/// 检查射线与碰撞体相交
/// </summary>
private float CheckRayCollider(Vector2 origin, Vector2 direction, Collider2D collider)
{
// 简化实现,实际项目中需要根据碰撞体类型实现
if (collider is BoxCollider2D box)
{
return RayBoxIntersection(origin, direction, box);
}
else if (collider is CircleCollider2D circle)
{
return RayCircleIntersection(origin, direction, circle);
}
return -1f;
}
/// <summary>
/// 射线与盒子相交检测
/// </summary>
private float RayBoxIntersection(Vector2 origin, Vector2 direction, BoxCollider2D box)
{
Bounds bounds = box.bounds;
Vector2 min = bounds.min;
Vector2 max = bounds.max;
float tMin = (min.x - origin.x) / direction.x;
float tMax = (max.x - origin.x) / direction.x;
if (tMin > tMax) Swap(ref tMin, ref tMax);
float tyMin = (min.y - origin.y) / direction.y;
float tyMax = (max.y - origin.y) / direction.y;
if (tyMin > tyMax) Swap(ref tyMin, ref tyMax);
if (tMin > tyMax || tyMin > tMax)
return -1f;
if (tyMin > tMin) tMin = tyMin;
if (tyMax < tMax) tMax = tyMax;
if (tMax < 0)
return -1f;
return tMin >= 0 ? tMin : tMax;
}
/// <summary>
/// 射线与圆形相交检测
/// </summary>
private float RayCircleIntersection(Vector2 origin, Vector2 direction, CircleCollider2D circle)
{
Vector2 center = circle.bounds.center;
float radius = circle.radius * Mathf.Max(
circle.transform.lossyScale.x,
circle.transform.lossyScale.y
);
Vector2 toCircle = center - origin;
float tca = Vector2.Dot(toCircle, direction);
if (tca < 0)
return -1f;
float d2 = Vector2.Dot(toCircle, toCircle) - tca * tca;
float radius2 = radius * radius;
if (d2 > radius2)
return -1f;
float thc = Mathf.Sqrt(radius2 - d2);
float t0 = tca - thc;
float t1 = tca + thc;
if (t0 > t1) Swap(ref t0, ref t1);
if (t0 < 0)
{
t0 = t1;
if (t0 < 0)
return -1f;
}
return t0;
}
/// <summary>
/// 计算射线投射法线
/// </summary>
private Vector2 CalculateRaycastNormal(Vector2 origin, Vector2 direction, Collider2D collider, float distance)
{
Vector2 hitPoint = origin + direction * distance;
if (collider is CircleCollider2D circle)
{
Vector2 center = circle.bounds.center;
return (hitPoint - center).normalized;
}
else if (collider is BoxCollider2D box)
{
Bounds bounds = box.bounds;
Vector2 localPoint = hitPoint - (Vector2)bounds.center;
// 找到最近的面
float minDist = Mathf.Min(
Mathf.Abs(bounds.max.x - hitPoint.x),
Mathf.Abs(hitPoint.x - bounds.min.x),
Mathf.Abs(bounds.max.y - hitPoint.y),
Mathf.Abs(hitPoint.y - bounds.min.y)
);
if (Mathf.Abs(bounds.max.x - hitPoint.x) == minDist)
return Vector2.right;
else if (Mathf.Abs(hitPoint.x - bounds.min.x) == minDist)
return Vector2.left;
else if (Mathf.Abs(bounds.max.y - hitPoint.y) == minDist)
return Vector2.up;
else
return Vector2.down;
}
return -direction;
}
private void Swap(ref float a, ref float b)
{
float temp = a;
a = b;
b = temp;
}
/// <summary>
/// 重叠检测
/// </summary>
public OverlapResult2D OverlapArea(Vector2 point, Vector2 size, int layerMask)
{
OverlapResult2D result = new OverlapResult2D();
result.Colliders = new List<Collider2D>();
Bounds areaBounds = new Bounds(point, size);
foreach (var collider in registeredColliders)
{
if (collider == null)
continue;
// 检查层级
if ((layerMask & (1 << collider.gameObject.layer)) == 0)
continue;
// 检查重叠
if (areaBounds.Intersects(colliderData[collider].WorldBounds))
{
result.Colliders.Add(collider);
result.HasOverlap = true;
}
}
return result;
}
/// <summary>
/// 获取碰撞体数据
/// </summary>
public ColliderData GetColliderData(Collider2D collider)
{
if (colliderData.ContainsKey(collider))
{
return colliderData[collider];
}
return null;
}
}
/// <summary>
/// 碰撞体对
/// </summary>
public struct ColliderPair
{
public Collider2D A;
public Collider2D B;
public ColliderPair(Collider2D a, Collider2D b)
{
A = a;
B = b;
}
}
/// <summary>
/// 射线检测结果
/// </summary>
public struct RaycastResult2D
{
public bool Hit;
public float Distance;
public Vector2 Point;
public Vector2 Normal;
public Collider2D Collider;
}
/// <summary>
/// 重叠检测结果
/// </summary>
public struct OverlapResult2D
{
public bool HasOverlap;
public List<Collider2D> Colliders;
}
/// <summary>
/// 空间哈希网格
/// </summary>
public class SpatialHashGrid
{
private Dictionary<Vector2Int, List<Collider2D>> grid;
private float cellSize;
public SpatialHashGrid(float cellSize)
{
this.cellSize = cellSize;
grid = new Dictionary<Vector2Int, List<Collider2D>>();
}
public void Add(Collider2D collider, Bounds bounds)
{
Vector2Int minCell = GetCellCoordinate(bounds.min);
Vector2Int maxCell = GetCellCoordinate(bounds.max);
for (int x = minCell.x; x <= maxCell.x; x++)
{
for (int y = minCell.y; y <= maxCell.y; y++)
{
Vector2Int cell = new Vector2Int(x, y);
if (!grid.ContainsKey(cell))
{
grid[cell] = new List<Collider2D>();
}
if (!grid[cell].Contains(collider))
{
grid[cell].Add(collider);
}
}
}
}
public void Remove(Collider2D collider)
{
foreach (var cell in grid)
{
cell.Value.Remove(collider);
}
}
public void Clear()
{
grid.Clear();
}
public List<ColliderPair> GetPotentialPairs()
{
List<ColliderPair> pairs = new List<ColliderPair>();
HashSet<string> pairSet = new HashSet<string>();
foreach (var cell in grid.Values)
{
if (cell.Count < 2)
continue;
for (int i = 0; i < cell.Count; i++)
{
for (int j = i + 1; j < cell.Count; j++)
{
string pairId = GetPairId(cell[i], cell[j]);
if (!pairSet.Contains(pairId))
{
pairSet.Add(pairId);
pairs.Add(new ColliderPair(cell[i], cell[j]));
}
}
}
}
return pairs;
}
private Vector2Int GetCellCoordinate(Vector2 point)
{
return new Vector2Int(
Mathf.FloorToInt(point.x / cellSize),
Mathf.FloorToInt(point.y / cellSize)
);
}
private string GetPairId(Collider2D a, Collider2D b)
{
int idA = a.GetInstanceID();
int idB = b.GetInstanceID();
if (idA < idB)
{
return $"{idA}_{idB}";
}
else
{
return $"{idB}_{idA}";
}
}
}
/// <summary>
/// 四叉树
/// </summary>
public class QuadTree
{
private class QuadTreeNode
{
public Rect Bounds;
public int Capacity;
public List<Collider2D> Colliders;
public QuadTreeNode[] Children;
public bool IsDivided;
public QuadTreeNode(Rect bounds, int capacity)
{
Bounds = bounds;
Capacity = capacity;
Colliders = new List<Collider2D>();
Children = new QuadTreeNode[4];
IsDivided = false;
}
public bool Insert(Collider2D collider, Bounds bounds)
{
if (!Bounds.Overlaps(new Rect(bounds.min, bounds.size)))
return false;
if (Colliders.Count < Capacity)
{
Colliders.Add(collider);
return true;
}
if (!IsDivided)
{
Subdivide();
}
for (int i = 0; i < 4; i++)
{
if (Children[i].Insert(collider, bounds))
{
return true;
}
}
// 如果无法插入到子节点,留在当前节点
Colliders.Add(collider);
return true;
}
private void Subdivide()
{
float halfWidth = Bounds.width * 0.5f;
float halfHeight = Bounds.height * 0.5f;
Children[0] = new QuadTreeNode(
new Rect(Bounds.x, Bounds.y + halfHeight, halfWidth, halfHeight),
Capacity
);
Children[1] = new QuadTreeNode(
new Rect(Bounds.x + halfWidth, Bounds.y + halfHeight, halfWidth, halfHeight),
Capacity
);
Children[2] = new QuadTreeNode(
new Rect(Bounds.x, Bounds.y, halfWidth, halfHeight),
Capacity
);
Children[3] = new QuadTreeNode(
new Rect(Bounds.x + halfWidth, Bounds.y, halfWidth, halfHeight),
Capacity
);
IsDivided = true;
}
public void Query(Bounds bounds, List<Collider2D> result)
{
if (!Bounds.Overlaps(new Rect(bounds.min, bounds.size)))
return;
foreach (var collider in Colliders)
{
result.Add(collider);
}
if (IsDivided)
{
for (int i = 0; i < 4; i++)
{
Children[i].Query(bounds, result);
}
}
}
public void Clear()
{
Colliders.Clear();
if (IsDivided)
{
for (int i = 0; i < 4; i++)
{
Children[i].Clear();
}
IsDivided = false;
}
}
}
private QuadTreeNode root;
public QuadTree(Rect bounds, int capacity)
{
root = new QuadTreeNode(bounds, capacity);
}
public void Insert(Collider2D collider, Bounds bounds)
{
root.Insert(collider, bounds);
}
public void Remove(Collider2D collider)
{
// 四叉树通常不清除单个元素
// 实际项目中需要更复杂的实现
}
public void Clear()
{
root.Clear();
}
public List<ColliderPair> GetPotentialPairs()
{
List<ColliderPair> pairs = new List<ColliderPair>();
HashSet<string> pairSet = new HashSet<string>();
// 查询每个碰撞体附近的碰撞体
foreach (var collider in GetAllColliders())
{
List<Collider2D> nearby = Query(collider.bounds);
foreach (var other in nearby)
{
if (other == collider)
continue;
string pairId = GetPairId(collider, other);
if (!pairSet.Contains(pairId))
{
pairSet.Add(pairId);
pairs.Add(new ColliderPair(collider, other));
}
}
}
return pairs;
}
private List<Collider2D> GetAllColliders()
{
List<Collider2D> allColliders = new List<Collider2D>();
CollectColliders(root, allColliders);
return allColliders;
}
private void CollectColliders(QuadTreeNode node, List<Collider2D> result)
{
result.AddRange(node.Colliders);
if (node.IsDivided)
{
for (int i = 0; i < 4; i++)
{
CollectColliders(node.Children[i], result);
}
}
}
private List<Collider2D> Query(Bounds bounds)
{
List<Collider2D> result = new List<Collider2D>();
root.Query(bounds, result);
return result;
}
private string GetPairId(Collider2D a, Collider2D b)
{
int idA = a.GetInstanceID();
int idB = b.GetInstanceID();
if (idA < idB)
{
return $"{idA}_{idB}";
}
else
{
return $"{idB}_{idA}";
}
}
}
}
7.5 小结
本章深入探讨了2D游戏开发中的数学原理和高级实现技术。我们从2D摄像机的投影矩阵和坐标变换开始,详细讲解了Sprite渲染的排序算法和裁切技术。这些基础数学概念是构建高质量2D游戏的关键。
在Sprite动画部分,我们探讨了关键帧插值算法和动画控制系统的实现。通过状态机和混合树,我们可以创建复杂的角色动画系统,这在商业级2D游戏中是必不可少的。
Tile地图系统部分展示了自动拼接算法和高级编辑功能的数学实现。通过规则Tile和自定义算法,我们可以创建复杂而美观的游戏地图。
2D碰撞检测部分深入研究了各种几何形状的相交检测算法,包括分离轴定理、空间分区优化等技术。这些算法不仅影响游戏的物理真实性,也直接关系到游戏的性能。
通过本章的学习,读者应该能够:
- 深入理解2D游戏开发中的核心数学原理
- 掌握商业级2D游戏系统的实现技术
- 能够根据项目需求定制和优化2D游戏组件
- 理解性能优化背后的数学和算法基础
在商业2D游戏开发中,数学不仅仅是理论,它是实现高效、美观、流畅游戏体验的实践工具。从精确的碰撞检测到平滑的动画插值,从智能的Tile拼接到优化的渲染排序,数学算法贯穿了整个开发过程。
掌握这些技术不仅能够解决当前的开发挑战,也为未来应对更复杂的2D游戏需求奠定了坚实的基础。无论是独立开发者还是大型游戏公司,这些知识和技能都是宝贵的资产。
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐


所有评论(0)