为机器人提供知觉
为机器人和AI代理提供有效的感知能力是开发智能系统的关键挑战。本章将探讨如何在Unity中实现机器人感知系统,包括障碍物检测和环境记忆功能,使AI能够更好地理解和导航其所处的环境。
8.1 回避障碍物
障碍物检测和回避是任何移动机器人或AI代理必须具备的基本能力。在游戏和模拟环境中,这些技术让AI角色能够自然、智能地在复杂环境中移动。
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 基础障碍物感知和回避系统
/// </summary>
public class ObstacleAvoidance : MonoBehaviour
{
[Header("感知设置")]
public float detectionRadius = 5.0f;
public float detectionAngle = 120.0f;
public int rayCount = 8;
public LayerMask obstacleLayer;
[Header("移动设置")]
public float moveSpeed = 3.0f;
public float rotationSpeed = 120.0f;
public float avoidanceWeight = 1.5f;
public float targetWeight = 1.0f;
[Header("目标设置")]
public Transform target;
// 调试可视化
public bool showDebugRays = true;
// 存储探测结果
private List<RaycastHit> detectedObstacles = new List<RaycastHit>();
private Vector3 avoidanceDirection = Vector3.zero;
private Vector3 targetDirection = Vector3.zero;
void Update()
{
// 清除上一帧的探测结果
detectedObstacles.Clear();
// 探测周围障碍物
DetectObstacles();
// 计算回避方向
CalculateAvoidanceDirection();
// 计算朝向目标的方向
CalculateTargetDirection();
// 计算最终移动方向
Vector3 moveDirection = (avoidanceDirection * avoidanceWeight +
targetDirection * targetWeight).normalized;
// 应用移动
Move(moveDirection);
}
/// <summary>
/// 检测周围的障碍物
/// </summary>
void DetectObstacles()
{
// 围绕角色发射射线
for (int i = 0; i < rayCount; i++)
{
float angle = transform.eulerAngles.y - detectionAngle/2 +
(detectionAngle * i / (rayCount - 1));
// 将角度转换为方向向量
Vector3 direction = Quaternion.Euler(0, angle, 0) * Vector3.forward;
// 发射射线
RaycastHit hit;
if (Physics.Raycast(transform.position, direction, out hit,
detectionRadius, obstacleLayer))
{
// 记录检测到的障碍物
detectedObstacles.Add(hit);
// 绘制调试射线
if (showDebugRays)
Debug.DrawLine(transform.position, hit.point, Color.red);
}
else if (showDebugRays)
{
// 绘制未检测到障碍物的射线
Debug.DrawRay(transform.position, direction * detectionRadius, Color.green);
}
}
}
/// <summary>
/// 根据检测到的障碍物计算回避方向
/// </summary>
void CalculateAvoidanceDirection()
{
// 如果没有检测到障碍物,则不需要回避
if (detectedObstacles.Count == 0)
{
avoidanceDirection = Vector3.zero;
return;
}
// 计算回避向量
Vector3 avoidance = Vector3.zero;
foreach (RaycastHit hit in detectedObstacles)
{
// 计算从障碍物到角色的方向
Vector3 fromObstacle = transform.position - hit.point;
// 回避力与距离成反比
float weight = 1.0f - (hit.distance / detectionRadius);
// 累加加权回避向量
avoidance += fromObstacle.normalized * weight;
}
// 正规化回避方向
avoidanceDirection = avoidance.normalized;
// 绘制回避方向
if (showDebugRays && avoidanceDirection != Vector3.zero)
{
Debug.DrawRay(transform.position, avoidanceDirection * 2, Color.yellow);
}
}
/// <summary>
/// 计算朝向目标的方向
/// </summary>
void CalculateTargetDirection()
{
if (target != null)
{
targetDirection = (target.position - transform.position).normalized;
// 绘制目标方向
if (showDebugRays)
{
Debug.DrawRay(transform.position, targetDirection * 2, Color.blue);
}
}
else
{
targetDirection = transform.forward;
}
}
/// <summary>
/// 根据计算的方向移动角色
/// </summary>
/// <param name="direction">移动方向</param>
void Move(Vector3 direction)
{
// 朝向目标方向旋转
if (direction != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
// 向前移动
transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
}
/// <summary>
/// 设置导航目标
/// </summary>
/// <param name="newTarget">新的目标变换</param>
public void SetTarget(Transform newTarget)
{
target = newTarget;
}
/// <summary>
/// 在编辑器中可视化检测范围
/// </summary>
void OnDrawGizmosSelected()
{
// 绘制检测半径
Gizmos.color = Color.cyan;
// 绘制扇形检测区域
Vector3 leftRay = Quaternion.Euler(0, -detectionAngle/2, 0) * Vector3.forward;
Vector3 rightRay = Quaternion.Euler(0, detectionAngle/2, 0) * Vector3.forward;
Gizmos.DrawLine(transform.position, transform.position + leftRay * detectionRadius);
Gizmos.DrawLine(transform.position, transform.position + rightRay * detectionRadius);
// 绘制半圆弧
int segments = 20;
Vector3 prev = transform.position + leftRay * detectionRadius;
for (int i = 1; i <= segments; i++)
{
float angle = -detectionAngle/2 + (detectionAngle * i / segments);
Vector3 direction = Quaternion.Euler(0, angle, 0) * Vector3.forward;
Vector3 current = transform.position + direction * detectionRadius;
Gizmos.DrawLine(prev, current);
prev = current;
}
}
}
这个基础障碍物回避系统使用射线检测来感知周围环境,并根据检测结果计算回避方向。它还考虑了导航目标,通过平衡回避力和目标吸引力来确定最终移动方向。该系统具有良好的可视化功能,便于调试和优化。
8.1.1 探测环境
为了有效地感知环境,我们需要实现更先进的探测技术。以下是一个增强版的环境探测系统,它使用多种传感器模拟来提供更全面的环境感知:
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 高级环境探测系统,模拟多种传感器
/// </summary>
public class EnvironmentSensor : MonoBehaviour
{
[System.Serializable]
public class SensorReading
{
public float distance;
public Vector3 hitPoint;
public Vector3 hitNormal;
public GameObject hitObject;
public float angle;
}
[Header("传感器配置")]
public enum SensorType
{
Raycast,
Spherecast,
Overlap
}
[System.Serializable]
public class Sensor
{
public string name = "Sensor";
public SensorType type = SensorType.Raycast;
public Vector3 localPosition;
public Vector3 localDirection = Vector3.forward;
public float distance = 5.0f;
public float radius = 0.5f;
public LayerMask detectionLayer;
public Color debugColor = Color.green;
public bool drawDebug = true;
[HideInInspector]
public SensorReading lastReading = new SensorReading();
}
public List<Sensor> sensors = new List<Sensor>();
[Header("高级探测设置")]
public bool enableDynamicDetection = true;
public float scanFrequency = 10f;
public bool detectMaterials = false;
public bool detectMovingObjects = true;
// 存储环境数据
public Dictionary<int, SensorReading> sensorData = new Dictionary<int, SensorReading>();
private float scanTimer = 0f;
void Start()
{
// 初始化传感器数据
for (int i = 0; i < sensors.Count; i++)
{
sensorData[i] = new SensorReading();
}
}
void Update()
{
// 定时扫描环境
if (enableDynamicDetection)
{
scanTimer += Time.deltaTime;
// 根据扫描频率进行检测
if (scanTimer >= 1f / scanFrequency)
{
ScanEnvironment();
scanTimer = 0f;
}
}
}
/// <summary>
/// 扫描环境
/// </summary>
public void ScanEnvironment()
{
for (int i = 0; i < sensors.Count; i++)
{
// 执行传感器检测
SensorReading reading = ScanWithSensor(sensors[i]);
// 更新传感器数据
sensors[i].lastReading = reading;
sensorData[i] = reading;
}
// 执行特殊环境检测
if (detectMovingObjects)
{
DetectMovingObjects();
}
}
/// <summary>
/// 使用单个传感器进行扫描
/// </summary>
SensorReading ScanWithSensor(Sensor sensor)
{
SensorReading reading = new SensorReading();
reading.distance = sensor.distance;
// 传感器的世界坐标和方向
Vector3 sensorPosition = transform.TransformPoint(sensor.localPosition);
Vector3 sensorDirection = transform.TransformDirection(sensor.localDirection).normalized;
bool hitDetected = false;
// 根据传感器类型执行不同的检测
switch (sensor.type)
{
case SensorType.Raycast:
RaycastHit rayHit;
if (Physics.Raycast(sensorPosition, sensorDirection, out rayHit,
sensor.distance, sensor.detectionLayer))
{
reading.distance = rayHit.distance;
reading.hitPoint = rayHit.point;
reading.hitNormal = rayHit.normal;
reading.hitObject = rayHit.collider.gameObject;
hitDetected = true;