Unity Movement AI (二)


         感兴趣的可以下载下来  跑一下(是下载源代码,不是release )

地址:  https://github.com/SunGuangdong/unity-movement-ai



        这个库有以下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 逃避,逃跑行为!

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);
        }
    }
}

速度匹配与之类似:
[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;
    }
}





猜你喜欢

转载自blog.csdn.net/u010019717/article/details/78079446