Unity Movement AI (二)
发表于2017-10-09
这个库有以下Movement AI :Arrive 抵达,Cohesion 凝聚,Collision Avoidance 碰撞避免,Evade 逃避,Flee 逃离,Follow Path 跟踪路径,Hide 隐藏,Interpose 插入(干预,管闲事),Offset Pursuit 偏移追求,Pursue 追求,Seek 寻求,Separation 分离,Velocity Match 速度匹配,Wall Avoidance 墙壁避免,andWander 漫游。
7 Evade 逃避;规避;逃脱
Evade 和 Flee 的区别,
首先 Evade 有使用到 Flee , 类似于 Pursue 使用 Seek 一样
真正的不同和意义是, 这个有预测的部分,根据目标的速度可以预测它的下一个位置在哪?
测试场景: 有意思,让 Seek 追着他, 然后 Evade 会逃脱
- public class EvadeUnit : MonoBehaviour
- {
- public Rigidbody target;
- private SteeringBasics steeringBasics;
- private Evade evade;
- void Start()
- {
- steeringBasics = GetComponent<SteeringBasics>();
- evade = GetComponent<Evade>();
- }
- void Update()
- {
- Vector3 accel = evade.getSteering(target);
- steeringBasics.steer(accel);
- steeringBasics.lookWhereYoureGoing();
- }
- }
- [RequireComponent(typeof(Flee))]
- public class Evade : MonoBehaviour
- {
- /* // 未来预测的最大预测时间 */
- public float maxPrediction = 1f;
- private Flee flee;
- // Use this for initialization
- void Start()
- {
- flee = GetComponent<Flee>();
- }
- public Vector3 getSteering(Rigidbody target)
- {
- /* 计算到目标的距离 */
- Vector3 displacement = target.position - transform.position;
- float distance = displacement.magnitude;
- /* 获得目标现在的速度 */
- float speed = target.velocity.magnitude;
- // 计算预测时间 (不能让预测时间能跑的距离 超过当前的距离)
- float prediction;
- if (speed <= distance / maxPrediction)
- {
- prediction = maxPrediction;
- }
- else
- {
- prediction = distance / speed;
- // 目标到达角色前,将预测位置在往前一点
- prediction *= 0.9f;
- }
- // 目标 : 在目标位置基础上添加预测的部分
- Vector3 explicitTarget = target.position target.velocity * prediction;
- // 使用之前的逃离功能
- return flee.getSteering(explicitTarget);
- }
- }
8 Interpose 干预;插入;调停
这个对象会想办法 走到两个 Wander 漫游对象中间 。
- public class InterposeUnit2 : MonoBehaviour {
- public Rigidbody target1;
- public Rigidbody target2;
- private SteeringBasics2 steeringBasics;
- private void Start()
- {
- steeringBasics = GetComponent<SteeringBasics2>();
- }
- private void Update()
- {
- Vector3 accel = steeringBasics.Interpose(target1, target2);
- steeringBasics.Steer(accel);
- steeringBasics.LookWhereYoureGoing();
- }
- }
SteeringBasics2 .cs 脚本中继续添加:
- public Vector3 Interpose(Rigidbody target1, Rigidbody target2)
- {
- Vector3 midPoint = (target1.position target2.position) / 2;
- // 到达 他俩中间需要的时间
- float timeToReachMidPoint = Vector3.Distance(midPoint, transform.position)/ maxVelocity;
- // 预测 当我到达后他们的位置
- Vector3 futureTarget1Pos = target1.position target1.velocity * timeToReachMidPoint;
- Vector3 futureTarget2Pos = target2.position target2.velocity * timeToReachMidPoint;
- midPoint = (futureTarget1Pos futureTarget2Pos) / 2;
- return Arrive(midPoint);
- }
9 WallAvoidance 就是躲避墙
提供了两个测试的 场景
- public class WallAvoidanceUnit2 : MonoBehaviour
- {
- // 编辑器下设置 路径
- public LinePath path;
- // 组件
- private SteeringBasics2 steeringBasics2;
- private WallAvoidance2 wallAvoidance2;
- private FollowPath followPath;
- private void Start()
- {
- path.calcDistances();
- steeringBasics2 = GetComponent<SteeringBasics2>();
- wallAvoidance2 = GetComponent<WallAvoidance2>();
- followPath = GetComponent<FollowPath>();
- }
- private void Update()
- {
- // 到达终点了, 原路返回
- if (IsAtEndOfPath())
- {
- path.reversePath();
- }
- // 得到加速度 (要躲避墙)
- Vector3 accel = wallAvoidance2.GetSteering();
- // 沿着路径走了 (约定没有碰撞时, 加速度为0 了)
- if (accel.magnitude < 0.005f)
- {
- accel = followPath.getSteering(path);
- }
- // 设置刚体 和 朝向
- steeringBasics2.Steer(accel);
- steeringBasics2.LookWhereYoureGoing();
- // 调试
- path.draw();
- }
- private bool IsAtEndOfPath()
- {
- return Vector3.Distance(path.endNode, transform.position) < followPath.stopRadius;
- }
- }
- [RequireComponent(typeof(SteeringBasics2))]
- public class WallAvoidance2 : MonoBehaviour
- {
- /* 前方射线应该延伸多远 */
- public float mainWhiskerLen = 1.25f;
- /* 跟墙保持的距离 */
- public float wallAvoidDistance = 0.5f;
- // 两边射线应该延伸多远
- public float sideWhiskerLen = 0.701f;
- // 两边射线的角度
- public float sideWhiskerAngle = 45f;
- public float maxAcceleration = 40f;
- // 组件
- private Rigidbody rb;
- private SteeringBasics steeringBasics;
- // Use this for initialization
- void Start()
- {
- rb = GetComponent<Rigidbody>();
- steeringBasics = GetComponent<SteeringBasics>();
- }
- public Vector3 GetSteering()
- {
- return GetSteering(rb.velocity);
- }
- public Vector3 GetSteering(Vector3 facingDir)
- {
- Vector3 acceleration = Vector3.zero;
- /* 创建射线方向向量 */
- Vector3[] rayDirs = new Vector3[3];
- // 自己前进的方向
- rayDirs[0] = facingDir.normalized;
- // 返回弧度, 对边y/临边x
- float orientation = Mathf.Atan2(rb.velocity.y, rb.velocity.x);
- // 两边的射线方向
- rayDirs[1] = OrientationToVector(orientation sideWhiskerAngle * Mathf.Deg2Rad);
- rayDirs[2] = OrientationToVector(orientation - sideWhiskerAngle * Mathf.Deg2Rad);
- RaycastHit hit;
- /* 如果没有碰撞,什么也不做 */
- if (!FindObstacle(rayDirs, out hit))
- {
- return acceleration;
- }
- /* 从墙上创建一个目标来 seek (这个方向和射线方向相反)*/
- Vector3 targetPostition = hit.point hit.normal * wallAvoidDistance;
- /* 如果速度和碰撞法线平行,则将目标向左或向右移动一点 (如果不矫正就会一直 垂直撞一个地方)*/
- Vector3 cross = Vector3.Cross(rb.velocity, hit.normal); // 叉乘 判断两个向量是否平行
- // 点乘“·”计算得到的结果是一个标量; 平行向量 normalized的点乘 是 -1 或者 1, 垂直是0
- // 叉乘“×”得到的结果是一个垂直于原向量构成平面的向量。 平行向量的叉乘是零向量
- if (cross.magnitude < 0.005f)
- {
- targetPostition = targetPostition new Vector3(-hit.normal.y, hit.normal.x, hit.normal.z);
- }
- // 返回最大加速度
- return steeringBasics.seek(targetPostition, maxAcceleration);
- }
- /* 将弧度 作为一个 单位向量返回 (极坐标公式)*/
- private Vector3 OrientationToVector(float orientation)
- {
- return new Vector3(Mathf.Cos(orientation), Mathf.Sin(orientation), 0);
- }
- /// <summary>
- /// 多个射线, 检测是否发生碰撞
- /// </summary>
- /// <param name="rayDirs"></param>
- /// <param name="firstHit"></param>
- /// <returns></returns>
- private bool FindObstacle(Vector3[] rayDirs, out RaycastHit firstHit)
- {
- firstHit = new RaycastHit();
- bool foundObs = false;
- for (int i = 0; i < rayDirs.Length; i )
- {
- float rayDist = (i == 0) ? mainWhiskerLen : sideWhiskerLen;
- RaycastHit hit;
- if (Physics.Raycast(transform.position, rayDirs[i], out hit, rayDist))
- {
- foundObs = true;
- firstHit = hit;
- break;
- }
- // 调试
- Debug.DrawLine(transform.position, transform.position rayDirs[i] * rayDist);
- }
- return foundObs;
- }
- }
10 OffsetPursuit 保持一定偏移的追逐
这个有点意思, 有些场景会用到, 那些白球可以以固定(每个白球相对目标都有设置固定值)的阵型 跟随红球。
Play 场景,那些白球 就会自动 调整站位 跟着红球移动!
每个白球都有一个子对象,上面的脚本是 : NearSensor .cs
- /// <summary>
- /// 接近传感器, 保存正在和我接触的,就是发生碰撞的
- /// </summary>
- public class NearSensor : MonoBehaviour {
- public HashSet<Rigidbody> targets = new HashSet<Rigidbody>();
- void OnTriggerEnter(Collider other) {
- targets.Add (other.GetComponent<Rigidbody>());
- }
- void OnTriggerExit(Collider other) {
- targets.Remove (other.GetComponent<Rigidbody>());
- }
- }
加速度会分为两方面, 成员之间 要分离(保持距离), 还要对主角保持偏移跟随。
组员之间分割脚本的处理如下:
- public class Separation : MonoBehaviour {
- /* 分隔 加速度 */
- public float sepMaxAcceleration = 25;
- /*
- 这应该是分离目标和自身之间可能的最大分离距离。 所以它应该是:分离传感器半径 最大目标半径(separation sensor radius max target radius )
- */
- public float maxSepDist = 1f;
- private float boundingRadius;
- // Use this for initialization
- void Start()
- {
- boundingRadius = SteeringBasics.getBoundingRadius(transform);
- }
- public Vector3 getSteering(ICollection<Rigidbody> targets)
- {
- Vector3 acceleration = Vector3.zero;
- // 只要存在发生碰撞的就要想办法分离
- foreach (Rigidbody r in targets)
- {
- /* 远离的方向 */
- Vector3 direction = transform.position - r.position;
- float dist = direction.magnitude;
- if (dist < maxSepDist)
- {
- float targetRadius = SteeringBasics.getBoundingRadius(r.transform);
- /* 计算分离强度(可改为使用平方反比,而不是线性) */
- var strength = sepMaxAcceleration * (maxSepDist - dist) / (maxSepDist - boundingRadius - targetRadius);
- /* 将分离加速度增加到现有的 Steer 上 (因为可能不是跟一个发生碰撞) */
- direction.Normalize();
- acceleration = direction * strength;
- }
- }
- return acceleration;
- }
- }
这个AI 最终使用的脚本!
- public class OffsetPursuitUnit : MonoBehaviour {
- public Rigidbody target;
- public Vector3 offset;
- public float groupLookDist = 1.5f;
- // 组件
- private SteeringBasics steeringBasics;
- private OffsetPursuit offsetPursuit;
- private Separation separation;
- private NearSensor sensor;
- void Start()
- {
- steeringBasics = GetComponent<SteeringBasics>();
- offsetPursuit = GetComponent<OffsetPursuit>();
- separation = GetComponent<Separation>();
- sensor = transform.Find("SeparationSensor").GetComponent<NearSensor>();
- }
- void LateUpdate()
- {
- Vector3 targetPos;
- // 偏移追随加速度 和 分隔加速度
- Vector3 offsetAccel = offsetPursuit.getSteering(target, offset, out targetPos);
- Vector3 sepAccel = separation.getSteering(sensor.targets);
- // 速度会 受到两个方面的影响
- steeringBasics.steer(offsetAccel sepAccel);
- /* 如果我们还在前往,那就要朝向我们要去的地方,其他的方向和我们的形成目标是一样的 */
- if (Vector3.Distance(transform.position, targetPos) > groupLookDist)
- {
- steeringBasics.lookWhereYoureGoing();
- } else
- {
- steeringBasics.lookAtDirection(target.rotation);
- }
- }
- }
处理 偏移追逐的 加速度
- [RequireComponent(typeof(SteeringBasics))]
- public class OffsetPursuit : MonoBehaviour {
- /*未来预测的最大预测时间*/
- public float maxPrediction = 1f;
- private Rigidbody rb;
- private SteeringBasics steeringBasics;
- // Use this for initialization
- void Start()
- {
- rb = GetComponent<Rigidbody>();
- steeringBasics = GetComponent<SteeringBasics>();
- }
- public Vector3 getSteering(Rigidbody target, Vector3 offset)
- {
- Vector3 targetPos;
- return getSteering(target, offset, out targetPos);
- }
- public Vector3 getSteering(Rigidbody target, Vector3 offset, out Vector3 targetPos)
- {
- // 得到世界坐标的偏移位置
- Vector3 worldOffsetPos = target.position target.transform.TransformDirection(offset);
- Debug.DrawLine(transform.position, worldOffsetPos);
- /* 计算距离到偏移点 */
- Vector3 displacement = worldOffsetPos - transform.position;
- float distance = displacement.magnitude;
- float speed = rb.velocity.magnitude;
- /* 预测的距离不要超过当前距离 */
- float prediction;
- if (speed <= distance / maxPrediction)
- {
- prediction = maxPrediction;
- }
- else
- {
- prediction = distance / speed;
- }
- /* 目标位置 */
- targetPos = worldOffsetPos target.velocity * prediction;
- return steeringBasics.arrive(targetPos);
- }
- }
11 Collision Avoidance 碰撞避免,冲突避免
之前有避免撞墙WallAvoidance, 现在是: CollisionAvoidance .cs
避免撞墙的处理 当时是通过3条射线检测 避免的! 两者还有一个区别, 墙是不动的, 但是这个新的方式是处理 两个都是运动的要怎么避免撞到一起!
那这个是怎么避免? 通过碰撞体 检测,如果有这个确实发生就处理一下就处理一下。
这些对象下面的 ColAvoidSensor 就是起到这个纪录用的(纪录当前与我发生碰撞的对象), 之前提到过。 NearSensor .cs
为了测试让他们运动轨迹是线段, 可以原路返回的 循环。 并且互相交错!
- public class ColAvoidUnit : MonoBehaviour {
- // 编辑器设置
- public LinePath path;
- // 组件
- private SteeringBasics steeringBasics;
- private FollowPath followPath;
- private CollisionAvoidance colAvoid;
- private NearSensor colAvoidSensor;
- void Start()
- {
- path.calcDistances();
- steeringBasics = GetComponent<SteeringBasics>();
- followPath = GetComponent<FollowPath>();
- colAvoid = GetComponent<CollisionAvoidance>();
- colAvoidSensor = transform.Find("ColAvoidSensor").GetComponent<NearSensor>();
- }
- void Update()
- {
- // 调试
- path.draw();
- // 是否原路返回
- if (isAtEndOfPath())
- {
- path.reversePath();
- }
- // 躲避 加速度
- Vector3 accel = colAvoid.GetSteering(colAvoidSensor.targets);
- // 不需要躲避 就沿着路径走
- if (accel.magnitude < 0.005f)
- {
- accel = followPath.getSteering(path);
- }
- // 设置刚体速度 和 朝向
- steeringBasics.Steer(accel);
- steeringBasics.LookWhereYoureGoing();
- }
- public bool isAtEndOfPath()
- {
- return Vector3.Distance(path.endNode, transform.position) < followPath.stopRadius;
- }
- }
- public class CollisionAvoidance : MonoBehaviour
- {
- public float maxAcceleration = 15f;
- // 角色半径
- private float characterRadius;
- private Rigidbody rb;
- void Start()
- {
- characterRadius = SteeringBasics.GetBoundingRadius(transform);
- rb = GetComponent<Rigidbody>();
- }
- public Vector3 GetSteering(ICollection<Rigidbody> targets)
- {
- Vector3 acceleration = Vector3.zero;
- /* 1. 找出这个角色将会与之碰撞的第一个目标 */
- /* 第一次碰撞时间 */
- float shortestTime = float.PositiveInfinity; // 正无穷大 临时值
- /* The first target that will collide and other data that
- * we will need and can avoid recalculating */
- // 重置数据 ,并且可以避免重新计算
- Rigidbody firstTarget = null;
- //float firstMinSeparation = 0, firstDistance = 0;
- float firstMinSeparation = 0,
- firstDistance = 0,
- firstRadius = 0;
- Vector3 firstRelativePos = Vector3.zero,
- firstRelativeVel = Vector3.zero;
- foreach (Rigidbody r in targets)
- {
- /* 计算碰撞时间 */
- // 相差位置
- Vector3 relativePos = transform.position - r.position;
- // 相差速度
- Vector3 relativeVel = rb.velocity - r.velocity;
- // 标量
- float distance = relativePos.magnitude;
- float relativeSpeed = relativeVel.magnitude;
- // 说明朝着相反的方向运动 并且速度一样
- if (relativeSpeed == 0)
- {
- continue;
- }
- //
- float timeToCollision = -1 * Vector3.Dot(relativePos, relativeVel) / (relativeSpeed * relativeSpeed);
- /* 检查它们是否会碰撞 */
- Vector3 separation = relativePos relativeVel * timeToCollision;
- float minSeparation = separation.magnitude;
- float targetRadius = SteeringBasics.GetBoundingRadius(r.transform);
- // 两者分离了
- if (minSeparation > characterRadius targetRadius)
- //if (minSeparation > 2 * agentRadius)
- {
- continue;
- }
- /* 检查它是否是最短, 是的话就纪录最短的 */
- if (timeToCollision > 0 && timeToCollision < shortestTime)
- {
- shortestTime = timeToCollision;
- firstTarget = r;
- firstMinSeparation = minSeparation;
- firstDistance = distance;
- firstRelativePos = relativePos;
- firstRelativeVel = relativeVel;
- firstRadius = targetRadius;
- }
- }
- /* 2. 计算加速度 */
- /* 如果没有目标,就退出 */
- if (firstTarget == null)
- {
- return acceleration;
- }
- /* If we are going to collide with no separation or if we are already colliding then
- * Steer based on current position */
- // 如果我们要在没有分离的情况下发生碰撞,或者如果我们已经碰撞了,然后根据当前位置进行碰撞
- if (firstMinSeparation <= 0 || firstDistance < characterRadius firstRadius)
- //if (firstMinSeparation <= 0 || firstDistance < 2 * agentRadius)
- {
- acceleration = transform.position - firstTarget.position;
- }
- /* 计算未来的相对位置 */
- else
- {
- acceleration = firstRelativePos firstRelativeVel * shortestTime;
- }
- /* 远离目标 */
- acceleration.Normalize();
- acceleration *= maxAcceleration;
- return acceleration;
- }
- }
12 Hide 躲藏
找到障碍然后躲起来。 自己和目标之前有障碍物
第一个脚本: WanderAvoidUnit .cs , CollisionAvoidance .cs 之前说过 Wander 漫步。 这个是在它基础上的升级, 漫步的过程中避免碰撞。 会检测我前进的方向上 出现障碍,那我就换一个方向
- public class WanderAvoidUnit : MonoBehaviour {
- private SteeringBasics steeringBasics;
- private Wander2 wander;
- private CollisionAvoidance colAvoid;
- // 用于纪录当前都有谁与我发生碰撞
- private NearSensor colAvoidSensor;
- void Start()
- {
- steeringBasics = GetComponent<SteeringBasics>();
- wander = GetComponent<Wander2>();
- colAvoid = GetComponent<CollisionAvoidance>();
- colAvoidSensor = transform.Find("ColAvoidSensor").GetComponent<NearSensor>();
- }
- // Update is called once per frame
- void Update()
- {
- // 加速度(有碰撞 就避免碰撞)
- Vector3 accel = colAvoid.GetSteering(colAvoidSensor.targets);
- // 没有任何碰撞的时候就 漫游
- if (accel.magnitude < 0.005f)
- {
- accel = wander.getSteering();
- }
- // 速度
- steeringBasics.steer(accel);
- // 朝向
- steeringBasics.lookWhereYoureGoing();
- }
- }
然后就是躲猫猫 脚本 HideUnit .cs Hide .cs
还有躲猫猫实际上是一种 Evade 逃避,逃跑行为!
copy- public class HideUnit : MonoBehaviour {
- public Rigidbody target;
- private SteeringBasics steeringBasics;
- private Hide hide;
- private Spawner obstacleSpawner;
- private WallAvoidance wallAvoid;
- void Start()
- {
- steeringBasics = GetComponent<SteeringBasics>();
- hide = GetComponent<Hide>();
- obstacleSpawner = GameObject.Find("ObstacleSpawner").GetComponent<Spawner>();
- wallAvoid = GetComponent<WallAvoidance>();
- }
- void Update()
- {
- // 得到加速度 躲在障碍物后面,同时不被目标看到
- Vector3 hidePosition;
- Vector3 hideAccel = hide.GetSteering(target, obstacleSpawner.objs, out hidePosition);
- // 如果撞墙要 解决
- Vector3 accel = wallAvoid.GetSteering(hidePosition - transform.position);
- // 没有撞墙 (说明如果撞墙先解决撞墙)
- if (accel.magnitude < 0.005f)
- {
- accel = hideAccel;
- }
- // 设置速度 和 朝向
- steeringBasics.Steer(accel);
- steeringBasics.LookWhereYoureGoing();
- }
- }
- [RequireComponent(typeof(SteeringBasics))]
- [RequireComponent(typeof(Evade))]
- public class Hide : MonoBehaviour {
- // 边界距离 (距离障碍物 边的距离)
- public float distanceFromBoundary = 0.6f;
- // 组件
- private SteeringBasics steeringBasics;
- private Evade evade;
- // Use this for initialization
- void Start () {
- steeringBasics = GetComponent<SteeringBasics>();
- evade = GetComponent<Evade>();
- }
- public Vector3 GetSteering(Rigidbody target, ICollection<Rigidbody> obstacles, out Vector3 bestHidingSpot)
- {
- // 临时值
- float distToClostest = Mathf.Infinity;
- bestHidingSpot = Vector3.zero;
- // 找到最近的隐藏点 , 遍历所有障碍
- foreach(Rigidbody r in obstacles)
- {
- // 这个障碍 可以作为隐蔽的位置
- Vector3 hidingSpot = GetHidingPosition(r, target);
- // 离我多远
- float dist = Vector3.Distance(hidingSpot, transform.position);
- // 最近就保存
- if(dist < distToClostest)
- {
- distToClostest = dist;
- bestHidingSpot = hidingSpot;
- }
- }
- //如果没有发现隐藏点,就只躲避敌人 (比如我和敌人都处于所有障碍的一侧)
- if (distToClostest == Mathf.Infinity)
- {
- return evade.GetSteering(target);
- }
- Debug.DrawLine(transform.position, bestHidingSpot);
- // 返回加速度
- return steeringBasics.Arrive(bestHidingSpot);
- }
- // 获取隐藏位置
- private Vector3 GetHidingPosition(Rigidbody obstacle, Rigidbody target)
- {
- // 这个障碍物 附近
- float distAway = SteeringBasics.GetBoundingRadius(obstacle.transform) distanceFromBoundary;
- // 目标看障碍物的方向(就要躲在这个方向上)
- Vector3 dir = obstacle.position - target.position;
- dir.Normalize();
- // 最终隐藏位置
- return obstacle.position dir * distAway;
- }
- }
随机生成障碍的脚本:
- public class Spawner : MonoBehaviour {
- // Prefab
- public Transform obj;
- // 用于随机的范围
- public Vector2 objectSizeRange = new Vector2(1, 2);
- // 一次性创建多少个
- public int numberOfObjects = 10;
- // 是否随机方向
- public bool randomizeOrientation = false;
- // 生成的内容要在 屏幕边界内
- public float boundaryPadding = 1f;
- // 生成的内容 距离现有的对象 要保持的最小距离
- public float spaceBetweenObjects = 1f;
- public Transform[] thingsToAvoid;
- private Vector3 bottomLeft;
- private Vector3 widthHeight;
- private float[] thingsToAvoidRadius;
- // 纪录现有生成的对象
- [System.NonSerialized]
- public List<Rigidbody> objs = new List<Rigidbody>();
- void Start()
- {
- // 得到屏幕大小
- float z = -1 * Camera.main.transform.position.z;
- bottomLeft = Camera.main.ViewportToWorldPoint(new Vector3(0, 0, z));
- Vector3 topRight = Camera.main.ViewportToWorldPoint(new Vector3(1, 1, z));
- widthHeight = topRight - bottomLeft;
- // 如果需要的话 要避免和场景内其他对象重叠 纪录他们的数据
- thingsToAvoidRadius = new float[thingsToAvoid.Length];
- for (int i = 0; i < thingsToAvoid.Length; i )
- {
- thingsToAvoidRadius[i] = SteeringBasics.GetBoundingRadius(thingsToAvoid[i].transform);
- }
- // 创建就行了
- for (int i = 0; i < numberOfObjects; i )
- {
- // 每次不一定创建成功,这里增加概率
- for(int j = 0; j < 10; j )
- {
- if(TryToCreateObject())
- {
- break;
- }
- }
- }
- }
- /// <summary>
- /// 尝试创建对象
- /// </summary>
- /// <returns></returns>
- private bool TryToCreateObject()
- {
- // 随机位置 和 大小
- float size = Random.Range(objectSizeRange.x, objectSizeRange.y);
- float halfSize = size / 2f;
- Vector3 pos = new Vector3();
- pos.x = bottomLeft.x Random.Range(boundaryPadding halfSize, widthHeight.x - boundaryPadding - halfSize);
- pos.y = bottomLeft.y Random.Range(boundaryPadding halfSize, widthHeight.y - boundaryPadding - halfSize);
- // 这个位置可以方式那就实例化
- if(CanPlaceObject(halfSize, pos))
- {
- Transform t = Instantiate(obj, pos, Quaternion.identity) as Transform;
- t.localScale = new Vector3(size, size, obj.localScale.z);
- if(randomizeOrientation)
- {
- Vector3 euler = transform.eulerAngles;
- euler.z = Random.Range(0f, 360f);
- transform.eulerAngles = euler;
- }
- objs.Add(t.GetComponent<Rigidbody>());
- return true;
- }
- return false;
- }
- /// <summary>
- /// 判断是否可以放置, 主要判断是否重叠
- /// </summary>
- /// <param name="halfSize"></param>
- /// <param name="pos"></param>
- /// <returns></returns>
- private bool CanPlaceObject(float halfSize, Vector3 pos)
- {
- // 确保它不会与任何东西重叠
- for (int i = 0; i < thingsToAvoid.Length; i )
- {
- float dist = Vector3.Distance(thingsToAvoid[i].position, pos);
- if(dist < halfSize thingsToAvoidRadius[i])
- {
- return false;
- }
- }
- //确保它不会与任何现有对象重叠
- foreach (Rigidbody o in objs)
- {
- float dist = Vector3.Distance(o.position, pos);
- float oRadius = SteeringBasics.GetBoundingRadius(o.transform);
- if (dist < oRadius spaceBetweenObjects halfSize)
- {
- return false;
- }
- }
- return true;
- }
- }
13 Flocking集群
测试场景中有 4个 漫游并且避免碰撞的 对象。 他们属于搅局的, 可以看到他们和集群对象的行为差别。
Play 后会生成 75个对象随机分布在场景内。
集群对象都有什么呢?
集群处理 , 和 速度匹配, 同时要与其他对象保持间隔
- public class FlockingUnit : MonoBehaviour
- {
- // 参数
- public float cohesionWeight = 1.5f;
- public float separationWeight = 2f;
- public float velocityMatchWeight = 1f;
- // 速度
- private SteeringBasics steeringBasics;
- private Wander2 wander;
- private Cohesion cohesion;
- private Separation separation;
- private VelocityMatch velocityMatch;
- private NearSensor sensor;
- // Use this for initialization
- void Start()
- {
- steeringBasics = GetComponent<SteeringBasics>();
- wander = GetComponent<Wander2>();
- cohesion = GetComponent<Cohesion>();
- separation = GetComponent<Separation>();
- velocityMatch = GetComponent<VelocityMatch>();
- sensor = transform.Find("Sensor").GetComponent<NearSensor>();
- }
- // Update is called once per frame
- void Update()
- {
- Vector3 accel = Vector3.zero;
- // 集群加速度
- accel = cohesion.GetSteering(sensor.targets) * cohesionWeight;
- // 分隔加速度
- accel = separation.GetSteering(sensor.targets) * separationWeight;
- // 速度匹配加速度
- accel = velocityMatch.GetSteering(sensor.targets) * velocityMatchWeight;
- // 如果没有那些 影响, 就漫游好了
- if (accel.magnitude < 0.005f)
- {
- accel = wander.GetSteering();
- }
- // 设置刚体速度 和 朝向
- steeringBasics.Steer(accel);
- steeringBasics.LookWhereYoureGoing();
- }
- }
- [RequireComponent(typeof(SteeringBasics))]
- public class Cohesion : MonoBehaviour {
- // 我的前方视野
- public float facingCosine = 120f;
- private float facingCosineVal;
- private SteeringBasics steeringBasics;
- // Use this for initialization
- void Start () {
- facingCosineVal = Mathf.Cos(facingCosine * Mathf.Deg2Rad);
- steeringBasics = GetComponent<SteeringBasics>();
- }
- public Vector3 GetSteering(ICollection<Rigidbody> targets)
- {
- Vector3 centerOfMass = Vector3.zero;
- int count = 0;
- /* 得到我前方视野内所有角色的 中心 */
- foreach (Rigidbody r in targets)
- {
- // 在视野内 (视野是 无限扇形)
- if (steeringBasics.isFacing(r.position, facingCosineVal))
- {
- centerOfMass = r.position;
- count ;
- }
- }
- if (count == 0) // 我前面没有人。 漫游好了
- {
- return Vector3.zero;
- }
- else
- {
- // 目标目标位置
- centerOfMass = centerOfMass / count;
- return steeringBasics.Arrive(centerOfMass);
- }
- }
- }
速度匹配与之类似: copy
- [RequireComponent(typeof(SteeringBasics))]
- public class VelocityMatch : MonoBehaviour
- {
- // 视野范围
- public float facingCosine = 90;
- public float timeToTarget = 0.1f;
- public float maxAcceleration = 4f;
- // 视野的余弦值
- private float facingCosineVal;
- private Rigidbody rb;
- private SteeringBasics steeringBasics;
- // Use this for initialization
- void Start()
- {
- facingCosineVal = Mathf.Cos(facingCosine * Mathf.Deg2Rad);
- rb = GetComponent<Rigidbody>();
- steeringBasics = GetComponent<SteeringBasics>();
- }
- public Vector3 GetSteering(ICollection<Rigidbody> targets)
- {
- Vector3 accel = Vector3.zero;
- int count = 0;
- // 得到我视野内 所有人的 加速度平均值
- foreach (Rigidbody r in targets)
- {
- if (steeringBasics.isFacing(r.position, facingCosineVal))
- {
- /* 计算我们想要匹配这个目标的加速度 */
- Vector3 a = r.velocity - rb.velocity;
- /*
- Rather than accelerate the character to the correct speed in 1 second,
- accelerate so we reach the desired speed in timeToTarget seconds
- (if we were to actually accelerate for the full timeToTarget seconds).
- */
- // 而不是在1秒内加速字符的正确速度,这样我们就能在目标秒内达到所期望的速度(如果我们要在目标秒内加速的话)。
- a = a / timeToTarget;
- accel = a;
- count ;
- }
- }
- if (count > 0)
- {
- accel = accel / count;
- /* 不要超值 */
- if (accel.magnitude > maxAcceleration)
- {
- accel = accel.normalized * maxAcceleration;
- }
- }
- return accel;
- }
- }
http://blog.csdn.net/u010019717/article/details/78079446