protected void Move(Vector3 position3D, Vector3 deltaPosition) { bool positionDirty = false; if (controller != null && controller.enabled) { // Use CharacterController tr.position = position3D; controller.Move(deltaPosition); // Grab the position after the movement to be able to take physics into account // TODO: Add this into the clampedPosition calculation below to make RVO better respond to physics position3D = tr.position; if (controller.isGrounded) { verticalVelocity = 0; } } else { // Use Transform, Rigidbody or Rigidbody2D float lastElevation; movementPlane.ToPlane(position3D, out lastElevation); position3D += deltaPosition; // Position the character on the ground if (usingGravity) { position3D = RaycastPosition(position3D, lastElevation); } positionDirty = true; } // Clamp the position to the navmesh after movement is done var clampedPosition = ClampToNavmesh(position3D); // We cannot simply check for equality because some precision may be lost // if any coordinate transformations are used. if ((clampedPosition - position3D).sqrMagnitude > 0.001f * 0.001f) { // The agent was outside the navmesh. Remove that component of the velocity // so that the velocity only goes along the direction of the wall, not into it var difference = movementPlane.ToPlane(clampedPosition - position3D); velocity2D -= difference * Vector2.Dot(difference, velocity2D) / difference.sqrMagnitude; // Make sure the RVO system knows that there was a collision here // Otherwise other agents may think this agent continued // to move forwards and avoidance quality may suffer if (rvoController != null && rvoController.enabled) { rvoController.SetCollisionNormal(difference); } position3D = clampedPosition; positionDirty = true; } // Assign the final position to the character if we haven't already set it if (positionDirty) { // Note that rigid.MovePosition may or may not move the character immediately. // Check the Unity documentation for the special cases. if (rigid != null) { rigid.MovePosition(position3D); } else if (rigid2D != null) { rigid2D.MovePosition(position3D); } else { tr.position = position3D; } } }
/** Update is called once per frame */ protected virtual void Update() { deltaTime = Mathf.Min(Time.smoothDeltaTime * 2, Time.deltaTime); if (rp != null) { RichPathPart currentPart = rp.GetCurrentPart(); var fn = currentPart as RichFunnel; if (fn != null) { // Clamp the current position to the navmesh // and update the list of upcoming corners in the path // and store that in the 'nextCorners' variable Vector3 position = UpdateTarget(fn); // Only get walls every 5th frame to save on performance if (Time.frameCount % 5 == 0 && wallForce > 0 && wallDist > 0) { wallBuffer.Clear(); fn.FindWalls(wallBuffer, wallDist); } // Target point int tgIndex = 0; Vector3 targetPoint = nextCorners[tgIndex]; Vector3 dir = targetPoint - position; dir.y = 0; bool passedTarget = Vector3.Dot(dir, currentTargetDirection) < 0; // Check if passed target in another way if (passedTarget && nextCorners.Count - tgIndex > 1) { tgIndex++; targetPoint = nextCorners[tgIndex]; } // Check if the target point changed compared to last frame if (targetPoint != lastTargetPoint) { currentTargetDirection = targetPoint - position; currentTargetDirection.y = 0; currentTargetDirection.Normalize(); lastTargetPoint = targetPoint; } // Direction to target dir = targetPoint - position; dir.y = 0; // Normalized direction Vector3 normdir = VectorMath.Normalize(dir, out distanceToWaypoint); // Is the endpoint of the path (part) the current target point bool targetIsEndPoint = lastCorner && nextCorners.Count - tgIndex == 1; // When very close to the target point, move directly towards the target // instead of using accelerations as they tend to be a bit jittery in this case if (targetIsEndPoint && distanceToWaypoint < 0.01f * maxSpeed) { // Velocity will be at most 1 times max speed, it will be further clamped below velocity = (targetPoint - position) * 100; } else { // Calculate force from walls Vector3 wallForceVector = CalculateWallForce(position, normdir); Vector2 accelerationVector; if (targetIsEndPoint) { accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), Vector2.zero, To2D(velocity)); //accelerationVector = Vector3.ClampMagnitude(accelerationVector, acceleration); // Reduce the wall avoidance force as we get closer to our target wallForceVector *= System.Math.Min(distanceToWaypoint / 0.5f, 1); if (distanceToWaypoint < endReachedDistance) { // END REACHED NextPart(); } } else { var nextNextCorner = tgIndex < nextCorners.Count - 1 ? nextCorners[tgIndex + 1] : (targetPoint - position) * 2 + position; var targetVelocity = (nextNextCorner - targetPoint).normalized * maxSpeed; accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), To2D(targetVelocity), To2D(velocity)); } // Update the velocity using the acceleration velocity += (new Vector3(accelerationVector.x, 0, accelerationVector.y) + wallForceVector * wallForce) * deltaTime; } var currentNode = fn.CurrentNode; Vector3 closestOnNode; if (currentNode != null) { closestOnNode = currentNode.ClosestPointOnNode(position); } else { closestOnNode = position; } // Distance to the end of the path (as the crow flies) var distToEndOfPath = (fn.exactEnd - closestOnNode).magnitude; // Max speed to use for this frame var currentMaxSpeed = maxSpeed; currentMaxSpeed *= Mathf.Sqrt(Mathf.Min(1, distToEndOfPath / (maxSpeed * slowdownTime))); // Check if the agent should slow down in case it is not facing the direction it wants to move in if (slowWhenNotFacingTarget) { // 1 when normdir is in the same direction as tr.forward // 0.2 when they point in the opposite directions float directionSpeedFactor = Mathf.Max((Vector3.Dot(normdir, tr.forward) + 0.5f) / 1.5f, 0.2f); currentMaxSpeed *= directionSpeedFactor; float currentSpeed = VectorMath.MagnitudeXZ(velocity); float prevy = velocity.y; velocity.y = 0; currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed); // Make sure the agent always moves in the forward direction // except when getting close to the end of the path in which case // the velocity can be in any direction velocity = Vector3.Lerp(velocity.normalized * currentSpeed, tr.forward * currentSpeed, Mathf.Clamp(targetIsEndPoint ? distanceToWaypoint * 2 : 1, 0.0f, 0.5f)); velocity.y = prevy; } else { velocity = VectorMath.ClampMagnitudeXZ(velocity, currentMaxSpeed); } // Apply gravity velocity += deltaTime * gravity; if (rvoController != null && rvoController.enabled) { // Send a message to the RVOController that we want to move // with this velocity. In the next simulation step, this velocity // will be processed and it will be fed back the rvo controller // and finally it will be used by this script when calling the // CalculateMovementDelta method below // Make sure that we don't move further than to the end point of the path // If the RVO simulation FPS is low and we did not do this, the agent // might overshoot the target a lot. var rvoTarget = position + VectorMath.ClampMagnitudeXZ(velocity, distToEndOfPath); rvoController.SetTarget(rvoTarget, VectorMath.MagnitudeXZ(velocity), maxSpeed); } // Direction and distance to move during this frame Vector3 deltaPosition; if (rvoController != null && rvoController.enabled) { // Use RVOController to get a processed delta position // such that collisions will be avoided if possible deltaPosition = rvoController.CalculateMovementDelta(position, deltaTime); // The RVOController does not know about gravity // so we copy it from the normal velocity calculation deltaPosition.y = velocity.y * deltaTime; } else { deltaPosition = velocity * deltaTime; } if (targetIsEndPoint) { // Rotate towards the direction that the agent was in // when the target point was seen for the first time // TODO: Some magic constants here, should probably compute them from other variables // or expose them as separate variables Vector3 trotdir = Vector3.Lerp(deltaPosition.normalized, currentTargetDirection, System.Math.Max(1 - distanceToWaypoint * 2, 0)); RotateTowards(trotdir); } else { // Rotate towards the direction we are moving in RotateTowards(deltaPosition); } if (controller != null && controller.enabled) { // Use CharacterController tr.position = position; controller.Move(deltaPosition); // Grab the position after the movement to be able to take physics into account position = tr.position; } else { // Use Transform float lastY = position.y; position += deltaPosition; // Position the character on the ground position = RaycastPosition(position, lastY); } // Clamp the position to the navmesh after movement is done var clampedPosition = fn.ClampToNavmesh(position); if (position != clampedPosition) { // The agent was outside the navmesh. Remove that component of the velocity // so that the velocity only goes along the direction of the wall, not into it var difference = clampedPosition - position; velocity -= difference * Vector3.Dot(difference, velocity) / difference.sqrMagnitude; // Make sure the RVO system knows that there was a collision here // Otherwise other agents may think this agent continued to move forwards // and avoidance quality may suffer if (rvoController != null && rvoController.enabled) { rvoController.SetCollisionNormal(difference); } } tr.position = clampedPosition; } else { if (rvoController != null && rvoController.enabled) { //Use RVOController rvoController.Move(Vector3.zero); } } if (currentPart is RichSpecial) { // The current path part is a special part, for example a link // Movement during this part of the path is handled by the TraverseSpecial coroutine if (!traversingSpecialPath) { StartCoroutine(TraverseSpecial(currentPart as RichSpecial)); } } } else { if (rvoController != null && rvoController.enabled) { // Use RVOController rvoController.Move(Vector3.zero); } else if (controller != null && controller.enabled) { } else { tr.position = RaycastPosition(tr.position, tr.position.y); } } }