第一部分:为什么需要射线与几何检测?
1. 游戏世界的"触觉"
在现实世界中,我们通过触觉感知物体。在游戏世界中,射线和几何检测就是我们的"虚拟触觉",让代码能够感知和响应游戏世界的几何结构。
2. 三个核心问题
射线和几何检测主要解决:
-
"前面有什么?"(视线检测、射击)
-
"我站在什么上面?"(地面检测)
-
"周围有什么?"(区域检测)
第二部分:射线基础 ------ 虚拟的激光笔
1. 射线的定义
射线是一条从起点向特定方向无限延伸的数学直线:
-
起点:射线开始的位置
-
方向:射线延伸的方向(通常已归一化)
csharp
// Unity中的Ray结构体
public struct Ray {
public Vector3 origin; // 起点
public Vector3 direction; // 方向(已归一化)
}
2. 创建射线的四种方法
方法1:手动创建
csharp
// 创建一个从(0,0,0)向上发射的射线
Ray ray = new Ray(Vector3.zero, Vector3.up);
// 或者使用构造函数
Ray ray2;
ray2.origin = transform.position;
ray2.direction = transform.forward;
方法2:从相机发射(最常用)
csharp
// 从屏幕坐标发射射线(用于鼠标点击)
Vector3 mousePosition = Input.mousePosition;
Ray mouseRay = Camera.main.ScreenPointToRay(mousePosition);
// 从视口坐标发射(范围0-1)
Ray viewportRay = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // 屏幕中心
// 实际应用:3D物体选择
void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
Debug.Log($"点击了: {hit.collider.gameObject.name}");
}
}
}
方法3:从物体发射
csharp
// 从物体位置向前发射
Ray forwardRay = new Ray(transform.position, transform.forward);
// 从特定偏移位置发射
Vector3 offset = new Vector3(0, 1, 0); // 头顶上方1单位
Ray offsetRay = new Ray(transform.position + offset, transform.forward);
方法4:两点确定一条射线
csharp
// 虽然Unity没有直接的方法,但我们可以计算
Vector3 start = transform.position;
Vector3 end = target.position;
Vector3 direction = (end - start).normalized;
Ray twoPointRay = new Ray(start, direction);
3. 可视化射线(调试关键)
csharp
// 方法1:Debug.DrawRay(只显示一帧)
Debug.DrawRay(transform.position, transform.forward * 10, Color.red);
// 方法2:Debug.DrawLine(显示线段)
Debug.DrawLine(startPoint, endPoint, Color.green, 2.0f); // 显示2秒
// 方法3:在编辑器中可视化
void OnDrawGizmos() {
Gizmos.color = Color.cyan;
// 绘制射线
Gizmos.DrawRay(transform.position, transform.forward * 5);
// 绘制球体表示起点
Gizmos.DrawSphere(transform.position, 0.1f);
}
// 实际应用:绘制武器射线
public class WeaponDebug : MonoBehaviour {
public float range = 100f;
void Update() {
// 绘制武器瞄准线
Debug.DrawRay(transform.position, transform.forward * range, Color.red);
// 如果按下开火键,显示命中点
if (Input.GetButton("Fire1")) {
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, range)) {
Debug.DrawLine(transform.position, hit.point, Color.yellow, 0.1f);
Debug.DrawSphere(hit.point, 0.1f, Color.yellow, 0.1f);
}
}
}
}
第三部分:射线检测 ------ Physics.Raycast 详解
1. 基础射线检测
csharp
// 最简单的形式:检测前方是否有物体
RaycastHit hitInfo; // 用于存储击中信息
float maxDistance = 100f;
if (Physics.Raycast(transform.position, transform.forward, out hitInfo, maxDistance)) {
// 检测到了碰撞体
Debug.Log($"击中了: {hitInfo.collider.gameObject.name}");
Debug.Log($"击中点: {hitInfo.point}");
Debug.Log($"距离: {hitInfo.distance}");
}
// 使用Ray结构体的形式
Ray ray = new Ray(transform.position, transform.forward);
if (Physics.Raycast(ray, out hitInfo, maxDistance)) {
// 同样有效
}
2. RaycastHit:击中信息详解
csharp
RaycastHit hit;
if (Physics.Raycast(ray, out hit)) {
// 1. 基本碰撞信息
GameObject hitObject = hit.collider.gameObject; // 击中的游戏对象
Transform hitTransform = hit.transform; // 击中的变换组件
Vector3 hitPoint = hit.point; // 世界空间中的击中点
Vector3 hitNormal = hit.normal; // 击中表面的法线方向
float distance = hit.distance; // 从射线起点到击中点的距离
// 2. 物理材质信息
PhysicMaterial physicMaterial = hit.collider.sharedMaterial;
// 3. 纹理坐标(UV)
Vector2 textureCoord = hit.textureCoord; // 主纹理UV坐标
Vector2 textureCoord2 = hit.textureCoord2; // 第二个纹理UV坐标
// 4. 三角形信息(仅MeshCollider)
int triangleIndex = hit.triangleIndex; // 击中的三角形索引
// 5. 刚体信息
Rigidbody hitRigidbody = hit.rigidbody; // 击中的刚体(如果有)
// 6. 碰撞器信息
Collider hitCollider = hit.collider; // 击中的碰撞器
}
3. LayerMask:选择性检测
csharp
// 创建LayerMask的几种方法
// 方法1:在Inspector中设置
public LayerMask targetLayers;
// 方法2:通过层名
int layerMask = LayerMask.GetMask("Enemy", "Environment");
// 方法3:通过层号
int enemyLayer = 8;
int environmentLayer = 9;
int layerMask2 = (1 << enemyLayer) | (1 << environmentLayer);
// 方法4:排除特定层
int allExceptLayer10 = ~(1 << 10); // 排除第10层
int allExceptUI = ~LayerMask.GetMask("UI");
// 使用LayerMask进行检测
if (Physics.Raycast(ray, out hit, maxDistance, layerMask)) {
// 只检测指定层的物体
}
// 实际应用:只检测可射击的物体
public class ShootingSystem : MonoBehaviour {
public LayerMask shootableLayers;
void Update() {
if (Input.GetButtonDown("Fire1")) {
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100f, shootableLayers)) {
// 只击中shootableLayers中的物体
if (hit.collider.CompareTag("Enemy")) {
hit.collider.GetComponent<EnemyHealth>().TakeDamage(25);
}
}
}
}
}
4. 查询触发器
csharp
// QueryTriggerInteraction 枚举
// - Ignore:忽略所有触发器
// - Collide:与触发器碰撞
// - UseGlobal:使用Physics设置
// 检测触发器
if (Physics.Raycast(ray, out hit, maxDistance, layerMask, QueryTriggerInteraction.Collide)) {
// 会检测到触发器
}
// 忽略触发器
if (Physics.Raycast(ray, out hit, maxDistance, layerMask, QueryTriggerInteraction.Ignore)) {
// 不会检测到触发器
}
第四部分:高级射线检测方法
1. RaycastAll:检测所有击中点
csharp
// 检测射线路径上的所有物体
RaycastHit[] allHits = Physics.RaycastAll(ray, maxDistance);
// 按距离排序(最近的先处理)
Array.Sort(allHits, (a, b) => a.distance.CompareTo(b.distance));
// 处理每个击中点
foreach (RaycastHit hit in allHits) {
Debug.Log($"击中: {hit.collider.name} 在距离 {hit.distance} 处");
// 实际应用:穿透射击
if (hit.collider.CompareTag("Enemy")) {
hit.collider.GetComponent<EnemyHealth>().TakeDamage(10);
}
}
// 实际应用:激光武器穿透多个目标
public class LaserWeapon : MonoBehaviour {
public LineRenderer laserLine;
public float range = 50f;
void Update() {
if (Input.GetButton("Fire1")) {
RaycastHit[] hits = Physics.RaycastAll(transform.position, transform.forward, range);
// 按距离排序
hits = hits.OrderBy(h => h.distance).ToArray();
// 创建激光点
Vector3[] linePoints = new Vector3[hits.Length + 1];
linePoints[0] = transform.position;
for (int i = 0; i < hits.Length; i++) {
linePoints[i + 1] = hits[i].point;
// 对每个击中的敌人造成伤害
if (hits[i].collider.CompareTag("Enemy")) {
hits[i].collider.GetComponent<EnemyHealth>().TakeDamage(25);
}
}
laserLine.positionCount = linePoints.Length;
laserLine.SetPositions(linePoints);
} else {
laserLine.positionCount = 0;
}
}
}
2. RaycastNonAlloc:避免GC分配
csharp
// 预分配数组,避免垃圾回收
private RaycastHit[] hitResults = new RaycastHit[10]; // 最多检测10个物体
void Update() {
// 返回实际击中的数量
int hitCount = Physics.RaycastNonAlloc(ray, hitResults, maxDistance);
for (int i = 0; i < hitCount; i++) {
Debug.Log($"击中: {hitResults[i].collider.name}");
}
}
// 实际应用:高性能射击检测
public class PerformanceShooting : MonoBehaviour {
private RaycastHit[] hitBuffer = new RaycastHit[32]; // 32个缓冲区
private int frameCount = 0;
void Update() {
frameCount++;
// 每10帧检测一次(减少检测频率)
if (frameCount % 10 == 0) {
int hits = Physics.RaycastNonAlloc(transform.position, transform.forward,
hitBuffer, 100f);
for (int i = 0; i < hits; i++) {
ProcessHit(hitBuffer[i]);
}
}
}
void ProcessHit(RaycastHit hit) {
// 处理击中逻辑
}
}
3. SphereCast:球形射线检测
csharp
// 沿射线方向检测一个移动的球体
float radius = 0.5f; // 球体半径
if (Physics.SphereCast(ray, radius, out hit, maxDistance)) {
// 检测到碰撞
}
// 另一种形式:指定起点
if (Physics.SphereCast(transform.position, radius, transform.forward, out hit, maxDistance)) {
// 检测到碰撞
}
// 实际应用:角色移动碰撞检测
public class CharacterCollision : MonoBehaviour {
public float characterRadius = 0.5f;
public float stepHeight = 0.3f;
void CheckMovement(Vector3 moveDirection, float moveDistance) {
RaycastHit hit;
// 使用SphereCast检测前方是否有障碍
if (Physics.SphereCast(transform.position, characterRadius,
moveDirection, out hit, moveDistance)) {
// 检查是否是可跨越的台阶
if (hit.distance < moveDistance && CanStepOver(hit.point)) {
// 跨越台阶
transform.position += Vector3.up * stepHeight;
} else {
// 被障碍物阻挡
float slideDistance = hit.distance - 0.1f; // 留一点空隙
transform.position += moveDirection.normalized * slideDistance;
}
} else {
// 无障碍物,自由移动
transform.position += moveDirection.normalized * moveDistance;
}
}
bool CanStepOver(Vector3 obstaclePoint) {
// 检测上方是否有空间
Vector3 checkPoint = obstaclePoint + Vector3.up * stepHeight;
return !Physics.CheckSphere(checkPoint, characterRadius);
}
}
4. CapsuleCast:胶囊体检测
csharp
// 定义胶囊体的两个半球中心
Vector3 point1 = transform.position + Vector3.up * 1f; // 顶部
Vector3 point2 = transform.position - Vector3.up * 1f; // 底部
float radius = 0.5f;
if (Physics.CapsuleCast(point1, point2, radius, transform.forward, out hit, maxDistance)) {
// 胶囊体在移动方向上检测到碰撞
}
// 实际应用:第三人称角色控制器
public class ThirdPersonController : MonoBehaviour {
public float height = 2f;
public float radius = 0.3f;
void CheckCapsuleCollision(Vector3 movement) {
Vector3 top = transform.position + Vector3.up * (height - radius);
Vector3 bottom = transform.position + Vector3.up * radius;
RaycastHit hit;
if (Physics.CapsuleCast(bottom, top, radius, movement.normalized, out hit, movement.magnitude)) {
// 尝试从侧面滑动
Vector3 slideDirection = Vector3.ProjectOnPlane(movement, hit.normal);
movement = slideDirection.normalized * movement.magnitude;
}
transform.position += movement;
}
}
5. BoxCast:盒形检测
csharp
// 定义盒子的参数
Vector3 center = transform.position;
Vector3 halfExtents = new Vector3(0.5f, 1f, 0.5f); // 盒子的一半尺寸
Quaternion orientation = transform.rotation; // 盒子的朝向
if (Physics.BoxCast(center, halfExtents, transform.forward, out hit, orientation, maxDistance)) {
// 盒子在移动方向上检测到碰撞
}
// 实际应用:车辆碰撞检测
public class VehicleCollision : MonoBehaviour {
public Vector3 boxSize = new Vector3(1f, 0.5f, 2f);
void FixedUpdate() {
// 检测前方
if (Physics.BoxCast(transform.position, boxSize * 0.5f, transform.forward,
out RaycastHit hit, transform.rotation, 5f)) {
// 根据距离调整速度
float speedFactor = Mathf.Clamp01(hit.distance / 5f);
ApplyBrakes(speedFactor);
}
}
void OnDrawGizmos() {
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
Gizmos.color = Color.red;
Gizmos.DrawWireCube(Vector3.zero, boxSize);
}
}
第五部分:重叠检测 ------ 区域检测
1. OverlapSphere:球形区域检测
csharp
// 检测球形区域内的所有碰撞体
Vector3 center = transform.position;
float radius = 5f;
Collider[] colliders = Physics.OverlapSphere(center, radius);
foreach (Collider collider in colliders) {
Debug.Log($"在范围内的物体: {collider.name}");
}
// 使用LayerMask过滤
Collider[] enemies = Physics.OverlapSphere(center, radius, LayerMask.GetMask("Enemy"));
// 实际应用:爆炸范围伤害
public class Explosion : MonoBehaviour {
public float explosionRadius = 10f;
public float explosionForce = 1000f;
public int damage = 50;
void Explode() {
Collider[] affectedObjects = Physics.OverlapSphere(transform.position, explosionRadius);
foreach (Collider col in affectedObjects) {
// 计算距离衰减
float distance = Vector3.Distance(transform.position, col.transform.position);
float damageMultiplier = 1f - Mathf.Clamp01(distance / explosionRadius);
// 应用伤害
Health health = col.GetComponent<Health>();
if (health != null) {
health.TakeDamage((int)(damage * damageMultiplier));
}
// 应用物理力
Rigidbody rb = col.GetComponent<Rigidbody>();
if (rb != null) {
Vector3 direction = (col.transform.position - transform.position).normalized;
rb.AddForce(direction * explosionForce * damageMultiplier);
}
}
// 可视化爆炸范围
Debug.DrawWireSphere(transform.position, explosionRadius, Color.red, 2f);
}
}
2. OverlapBox:盒形区域检测
csharp
// 检测盒形区域内的碰撞体
Vector3 center = transform.position;
Vector3 halfExtents = new Vector3(2f, 1f, 3f); // 盒子的一半尺寸
Quaternion orientation = transform.rotation;
Collider[] boxColliders = Physics.OverlapBox(center, halfExtents, orientation);
// 实际应用:门开关检测
public class DoorTrigger : MonoBehaviour {
public Vector3 detectionSize = new Vector3(1f, 2f, 0.5f);
void Update() {
Collider[] colliders = Physics.OverlapBox(transform.position, detectionSize * 0.5f, transform.rotation);
bool playerNearby = false;
foreach (Collider col in colliders) {
if (col.CompareTag("Player")) {
playerNearby = true;
break;
}
}
// 控制门开关
GetComponent<Door>().SetOpen(playerNearby);
}
void OnDrawGizmosSelected() {
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
Gizmos.color = new Color(0, 1, 0, 0.3f);
Gizmos.DrawCube(Vector3.zero, detectionSize);
}
}
3. OverlapCapsule:胶囊体区域检测
csharp
// 检测胶囊体区域内的碰撞体
Vector3 point0 = transform.position + Vector3.up * 1f;
Vector3 point1 = transform.position - Vector3.up * 1f;
float radius = 0.5f;
Collider[] capsuleColliders = Physics.OverlapCapsule(point0, point1, radius);
// 实际应用:近战攻击范围检测
public class MeleeAttack : MonoBehaviour {
public float attackRange = 1.5f;
public float attackHeight = 2f;
void Attack() {
Vector3 top = transform.position + transform.forward * attackRange + Vector3.up * attackHeight;
Vector3 bottom = transform.position + transform.forward * attackRange;
Collider[] hitColliders = Physics.OverlapCapsule(bottom, top, 0.5f);
foreach (Collider col in hitColliders) {
if (col.CompareTag("Enemy")) {
col.GetComponent<EnemyHealth>().TakeDamage(20);
}
}
}
}
4. NonAlloc版本的重叠检测
csharp
// 避免GC分配
private Collider[] overlapResults = new Collider[20];
void Update() {
int numColliders = Physics.OverlapSphereNonAlloc(transform.position, 5f, overlapResults);
for (int i = 0; i < numColliders; i++) {
// 处理每个碰撞体
}
}
第六部分:实际应用场景
场景1:第一人称射击系统
csharp
public class FPSWeapon : MonoBehaviour {
public float range = 100f;
public int damage = 25;
public float fireRate = 0.1f;
public ParticleSystem muzzleFlash;
public GameObject impactEffect;
private float nextFireTime = 0f;
void Update() {
if (Input.GetButton("Fire1") && Time.time >= nextFireTime) {
nextFireTime = Time.time + fireRate;
Shoot();
}
}
void Shoot() {
// 播放枪口效果
muzzleFlash.Play();
// 计算射线
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
// 射击检测
if (Physics.Raycast(ray, out hit, range)) {
// 命中效果
GameObject impact = Instantiate(impactEffect, hit.point, Quaternion.LookRotation(hit.normal));
Destroy(impact, 2f);
// 伤害处理
EnemyHealth enemy = hit.collider.GetComponent<EnemyHealth>();
if (enemy != null) {
// 计算伤害衰减(距离越远伤害越低)
float distance = hit.distance;
float damageMultiplier = Mathf.Clamp01(1f - distance / range);
int actualDamage = Mathf.RoundToInt(damage * damageMultiplier);
enemy.TakeDamage(actualDamage, hit.point);
// 如果是爆头
if (hit.collider.CompareTag("Head")) {
enemy.TakeCriticalDamage(actualDamage * 2);
}
}
// 处理可破坏物体
DestructibleObject destructible = hit.collider.GetComponent<DestructibleObject>();
if (destructible != null) {
destructible.TakeDamage(damage);
}
}
// 显示弹道(仅调试)
Debug.DrawRay(ray.origin, ray.direction * range, Color.red, 0.1f);
}
}
场景2:智能敌人AI视野系统
csharp
public class EnemyVision : MonoBehaviour {
public float sightRange = 20f;
public float fieldOfView = 90f;
public LayerMask targetMask;
public LayerMask obstacleMask;
private Transform player;
void Start() {
player = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update() {
if (CanSeePlayer()) {
Debug.Log("发现玩家!");
// 进入警戒状态
}
}
bool CanSeePlayer() {
if (player == null) return false;
Vector3 directionToPlayer = (player.position - transform.position);
float distanceToPlayer = directionToPlayer.magnitude;
// 距离检查
if (distanceToPlayer > sightRange) return false;
// 视野角度检查
float angleToPlayer = Vector3.Angle(transform.forward, directionToPlayer);
if (angleToPlayer > fieldOfView / 2) return false;
// 视线遮挡检查
RaycastHit hit;
if (Physics.Raycast(transform.position, directionToPlayer.normalized,
out hit, distanceToPlayer, obstacleMask)) {
// 如果有障碍物遮挡
return false;
}
// 最终确认:玩家是否在目标层
if (Physics.Raycast(transform.position, directionToPlayer.normalized,
out hit, distanceToPlayer, targetMask)) {
if (hit.collider.CompareTag("Player")) {
return true;
}
}
return false;
}
void OnDrawGizmosSelected() {
// 绘制视野范围
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, sightRange);
// 绘制视野锥
Vector3 leftBoundary = Quaternion.Euler(0, -fieldOfView / 2, 0) * transform.forward * sightRange;
Vector3 rightBoundary = Quaternion.Euler(0, fieldOfView / 2, 0) * transform.forward * sightRange;
Gizmos.color = Color.green;
Gizmos.DrawRay(transform.position, leftBoundary);
Gizmos.DrawRay(transform.position, rightBoundary);
Gizmos.DrawLine(transform.position + leftBoundary, transform.position + rightBoundary);
}
}
场景3:高级地面检测与斜坡处理
csharp
public class AdvancedGroundCheck : MonoBehaviour {
[Header("检测设置")]
public float groundCheckDistance = 0.2f;
public float groundCheckRadius = 0.3f;
public LayerMask groundLayer;
public float maxSlopeAngle = 45f;
[Header("状态")]
public bool isGrounded;
public bool isOnSlope;
public Vector3 groundNormal;
public float slopeAngle;
private CapsuleCollider capsule;
void Start() {
capsule = GetComponent<CapsuleCollider>();
}
void FixedUpdate() {
CheckGround();
}
void CheckGround() {
// 使用SphereCast进行更准确的地面检测
Vector3 rayStart = transform.position + Vector3.up * (capsule.height * 0.5f);
float castDistance = capsule.height * 0.5f + groundCheckDistance;
RaycastHit sphereHit;
bool sphereCastHit = Physics.SphereCast(rayStart, groundCheckRadius,
Vector3.down, out sphereHit,
castDistance, groundLayer);
// 使用Raycast作为备用
RaycastHit rayHit;
bool rayCastHit = Physics.Raycast(rayStart, Vector3.down,
out rayHit, castDistance, groundLayer);
if (sphereCastHit || rayCastHit) {
RaycastHit hit = sphereCastHit ? sphereHit : rayHit;
// 计算地面角度
groundNormal = hit.normal;
slopeAngle = Vector3.Angle(groundNormal, Vector3.up);
// 判断是否在地面上
isGrounded = slopeAngle <= maxSlopeAngle;
isOnSlope = slopeAngle > 5f && slopeAngle <= maxSlopeAngle;
if (isGrounded) {
// 调整角色位置,使其贴紧地面
float heightDifference = hit.distance - (capsule.height * 0.5f);
if (heightDifference < 0) {
transform.position += Vector3.up * -heightDifference;
}
// 如果是斜坡,计算斜坡方向
if (isOnSlope) {
Vector3 slopeDirection = Vector3.ProjectOnPlane(Vector3.forward, groundNormal).normalized;
// 使用slopeDirection进行斜坡移动
}
}
} else {
isGrounded = false;
isOnSlope = false;
groundNormal = Vector3.up;
slopeAngle = 0;
}
}
// 获取斜坡上的移动方向
public Vector3 GetSlopeMoveDirection(Vector3 inputDirection) {
if (isOnSlope) {
return Vector3.ProjectOnPlane(inputDirection, groundNormal).normalized;
}
return inputDirection.normalized;
}
void OnDrawGizmosSelected() {
if (capsule == null) return;
Vector3 rayStart = transform.position + Vector3.up * (capsule.height * 0.5f);
float castDistance = capsule.height * 0.5f + groundCheckDistance;
// 绘制检测范围
Gizmos.color = isGrounded ? Color.green : Color.red;
Gizmos.DrawWireSphere(rayStart, groundCheckRadius);
Gizmos.DrawLine(rayStart, rayStart + Vector3.down * castDistance);
// 绘制地面法线
if (isGrounded) {
Gizmos.color = Color.blue;
Gizmos.DrawRay(transform.position, groundNormal * 2);
}
}
}
场景4:3D对象选择与拖拽系统
csharp
public class ObjectSelector3D : MonoBehaviour {
public Material highlightMaterial;
public Material selectedMaterial;
public LayerMask selectableLayers;
private GameObject selectedObject;
private Material originalMaterial;
private Renderer objectRenderer;
private bool isDragging = false;
private Vector3 offset;
void Update() {
HandleSelection();
HandleDragging();
}
void HandleSelection() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity, selectableLayers)) {
// 清除之前的选择
ClearSelection();
// 选择新对象
selectedObject = hit.collider.gameObject;
objectRenderer = selectedObject.GetComponent<Renderer>();
if (objectRenderer != null) {
originalMaterial = objectRenderer.material;
objectRenderer.material = highlightMaterial;
}
// 计算拖拽偏移
Vector3 mouseWorldPos = GetMouseWorldPosition();
offset = selectedObject.transform.position - mouseWorldPos;
isDragging = true;
} else {
// 点击空白处,清除选择
ClearSelection();
}
}
}
void HandleDragging() {
if (isDragging && selectedObject != null && Input.GetMouseButton(0)) {
// 应用高亮材质
if (objectRenderer != null) {
objectRenderer.material = selectedMaterial;
}
// 获取鼠标在世界空间的位置
Vector3 mouseWorldPos = GetMouseWorldPosition();
// 使用射线投射确定拖拽平面
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane dragPlane = new Plane(Vector3.up, selectedObject.transform.position);
if (dragPlane.Raycast(ray, out float distance)) {
Vector3 targetPos = ray.GetPoint(distance);
selectedObject.transform.position = targetPos + offset;
}
}
// 释放鼠标时清除拖拽状态
if (Input.GetMouseButtonUp(0)) {
if (isDragging && objectRenderer != null) {
objectRenderer.material = highlightMaterial;
}
isDragging = false;
}
}
Vector3 GetMouseWorldPosition() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
if (groundPlane.Raycast(ray, out float distance)) {
return ray.GetPoint(distance);
}
return Vector3.zero;
}
void ClearSelection() {
if (selectedObject != null && objectRenderer != null) {
objectRenderer.material = originalMaterial;
}
selectedObject = null;
objectRenderer = null;
isDragging = false;
}
}
第七部分:性能优化与最佳实践
1. 分层检测策略
csharp
public class OptimizedDetection : MonoBehaviour {
[Header("检测层次")]
public bool useSimpleRaycast = true; // 使用简单射线(性能最好)
public bool useSphereCast = false; // 使用球体射线(更准确)
public bool useMultiRaycast = false; // 使用多重射线(最准确)
[Header("性能控制")]
public int maxChecksPerFrame = 10; // 每帧最大检测次数
public float checkInterval = 0.1f; // 检测间隔(秒)
private float lastCheckTime;
void Update() {
// 控制检测频率
if (Time.time - lastCheckTime < checkInterval) return;
lastCheckTime = Time.time;
if (useSimpleRaycast) {
PerformSimpleRaycast();
} else if (useSphereCast) {
PerformSphereCast();
} else if (useMultiRaycast) {
PerformMultiRaycast();
}
}
void PerformSimpleRaycast() {
// 最基本的射线检测
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, 10f)) {
ProcessDetection(hit);
}
}
void PerformSphereCast() {
// 使用SphereCast进行更准确的检测
RaycastHit hit;
if (Physics.SphereCast(transform.position, 0.5f, transform.forward, out hit, 10f)) {
ProcessDetection(hit);
}
}
void PerformMultiRaycast() {
// 使用多个射线进行最准确的检测
int checksThisFrame = 0;
float[] rayAngles = { -15f, 0f, 15f };
foreach (float angle in rayAngles) {
if (checksThisFrame >= maxChecksPerFrame) break;
Vector3 direction = Quaternion.Euler(0, angle, 0) * transform.forward;
RaycastHit hit;
if (Physics.Raycast(transform.position, direction, out hit, 10f)) {
ProcessDetection(hit);
}
checksThisFrame++;
}
}
void ProcessDetection(RaycastHit hit) {
// 处理检测结果
}
}
2. 对象池与缓存
csharp
public class RaycastCacheSystem : MonoBehaviour {
private Dictionary<GameObject, float> detectionCache = new Dictionary<GameObject, float>();
private float cacheDuration = 2f; // 缓存2秒
void Update() {
CheckForObjects();
CleanupCache();
}
void CheckForObjects() {
RaycastHit[] hits = new RaycastHit[20];
int hitCount = Physics.SphereCastNonAlloc(transform.position, 5f, Vector3.forward, hits);
for (int i = 0; i < hitCount; i++) {
GameObject obj = hits[i].collider.gameObject;
// 检查缓存
if (!detectionCache.ContainsKey(obj) ||
Time.time - detectionCache[obj] > cacheDuration) {
// 执行昂贵的检测逻辑
PerformExpensiveDetection(obj);
// 更新缓存
detectionCache[obj] = Time.time;
}
}
}
void PerformExpensiveDetection(GameObject obj) {
// 这里执行昂贵的检测逻辑
// 例如:视线检测、路径计算等
}
void CleanupCache() {
// 定期清理过期的缓存
List<GameObject> toRemove = new List<GameObject>();
foreach (var kvp in detectionCache) {
if (Time.time - kvp.Value > cacheDuration * 2) {
toRemove.Add(kvp.Key);
}
}
foreach (var obj in toRemove) {
detectionCache.Remove(obj);
}
}
}
3. 异步检测
csharp
public class AsyncRaycast : MonoBehaviour {
private bool isProcessing = false;
private RaycastHit[] asyncResults = new RaycastHit[10];
void Update() {
if (Input.GetKeyDown(KeyCode.Space) && !isProcessing) {
StartCoroutine(PerformAsyncRaycast());
}
}
IEnumerator PerformAsyncRaycast() {
isProcessing = true;
// 在主线程准备数据
Ray ray = new Ray(transform.position, transform.forward);
// 在后台线程执行(使用Job System或Task)
yield return StartCoroutine(RaycastAsync(ray, 100f));
// 在主线程处理结果
ProcessResults();
isProcessing = false;
}
IEnumerator RaycastAsync(Ray ray, float maxDistance) {
// 模拟耗时操作
yield return new WaitForSeconds(0.1f);
// 在实际项目中,这里可以使用Unity的Job System
// 或System.Threading.Tasks进行真正的并行计算
Physics.RaycastNonAlloc(ray, asyncResults, maxDistance);
}
void ProcessResults() {
foreach (var hit in asyncResults) {
if (hit.collider != null) {
Debug.Log($"异步检测到: {hit.collider.name}");
}
}
}
}
第八部分:常见问题与解决方案
问题1:射线检测不稳定(抖动)
csharp
public class StableRaycast : MonoBehaviour {
private RaycastHit lastHit;
private bool hasLastHit = false;
public float smoothingFactor = 0.5f;
void Update() {
RaycastHit currentHit;
if (Physics.Raycast(transform.position, transform.forward, out currentHit, 10f)) {
if (hasLastHit) {
// 使用插值平滑过渡
Vector3 smoothedPoint = Vector3.Lerp(lastHit.point, currentHit.point, smoothingFactor);
Vector3 smoothedNormal = Vector3.Lerp(lastHit.normal, currentHit.normal, smoothingFactor);
// 使用平滑后的结果
ProcessHit(smoothedPoint, smoothedNormal);
} else {
ProcessHit(currentHit.point, currentHit.normal);
}
lastHit = currentHit;
hasLastHit = true;
} else {
hasLastHit = false;
}
}
}
问题2:处理快速移动物体的检测
csharp
public class FastObjectDetection : MonoBehaviour {
public float speed = 20f;
private Vector3 lastPosition;
void Start() {
lastPosition = transform.position;
}
void FixedUpdate() {
Vector3 movement = transform.position - lastPosition;
// 使用CapsuleCast检测移动路径上的碰撞
RaycastHit hit;
if (Physics.CapsuleCast(lastPosition, transform.position, 0.5f,
movement.normalized, out hit, movement.magnitude)) {
// 处理碰撞
Debug.Log($"在移动过程中击中了: {hit.collider.name}");
// 反弹或停止
Vector3 reflection = Vector3.Reflect(movement, hit.normal);
transform.position = hit.point + reflection.normalized * 0.1f;
}
lastPosition = transform.position;
}
}
问题3:检测精度问题(缝隙)
csharp
public class PreciseDetection : MonoBehaviour {
public float skinWidth = 0.01f; // 皮肤宽度
bool CheckCollision(Vector3 position, Vector3 direction, float distance) {
// 多次检测提高精度
int numChecks = 3;
float checkDistance = distance / numChecks;
for (int i = 0; i < numChecks; i++) {
Vector3 checkPoint = position + direction * (checkDistance * i + skinWidth);
// 使用OverlapSphere检查微小重叠
if (Physics.OverlapSphere(checkPoint, skinWidth).Length > 0) {
return true;
}
}
return false;
}
}
总结:射线与几何检测的核心思维
1. 选择合适的检测方法
-
简单检测 :
Physics.Raycast(视线、射击) -
体积检测 :
Physics.SphereCast、Physics.CapsuleCast(角色移动) -
区域检测 :
Physics.OverlapSphere、Physics.OverlapBox(爆炸、触发器) -
精确检测:多重射线或组合使用
2. 性能优化要点
-
使用LayerMask:只检测需要的层
-
使用NonAlloc版本:避免GC分配
-
控制检测频率:不要每帧检测
-
缓存检测结果:重复利用结果
-
简化检测形状:使用最简单的形状
3. 调试技巧
-
可视化一切 :使用
Debug.DrawRay、Debug.DrawLine -
使用Gizmos:在Scene视图中绘制检测范围
-
记录日志:输出检测结果进行分析
-
性能分析:使用Profiler查看检测开销
4. 最终练习
创建一个完整的交互系统,包含:
-
鼠标点击选择物体
-
角色移动碰撞检测
-
敌人AI视野系统
-
武器射击系统
-
爆炸范围伤害
记住:射线和几何检测是游戏世界的"神经系统"。它们让游戏对象能够感知环境、做出反应,创造出沉浸式的交互体验。从简单的鼠标点击开始,逐步构建复杂的检测系统,你会逐渐掌握这门"与世界的交互"的艺术!