/// <summary> /// Determines if we'll treat input as if we're strafing /// </summary> protected void SimulateStrafeInput() { // Direction we need to travel in Vector3 lDirection = mWaypointVector; lDirection.y = lDirection.y - _PathHeight; lDirection.Normalize(); // Determine our view Vector3 lVerticalDirection = Vector3.Project(lDirection, _Transform.up); Vector3 lLateralDirection = (lDirection - lVerticalDirection).normalized; mInputFromAvatarAngle = Vector3Ext.SignedAngle(_Transform.forward, lLateralDirection); // Determine our movement if (mTargetDistance > _StopDistance) { // Calculate our own slowing float lRelativeMoveSpeed = 1f; if (mIsInSlowDistance && _SlowFactor > 0f) { float lSlowPercent = (mTargetDistance - _StopDistance) / (_SlowDistance - _StopDistance); lRelativeMoveSpeed = ((1f - _SlowFactor) * lSlowPercent) + _SlowFactor; } // TRT 4/5/2016: Force the slow distance as an absolute value if (mIsInSlowDistance && _SlowFactor > 0f) { lRelativeMoveSpeed = _SlowFactor; } lLateralDirection = _Transform.InverseTransformDirection(lLateralDirection); mMovementX = lLateralDirection.x * lRelativeMoveSpeed; mMovementY = lLateralDirection.z * lRelativeMoveSpeed; } // Grab extra input information if we can if (mMotionController._CameraTransform == null) { mInputFromCameraAngle = mInputFromAvatarAngle; } else { Vector3 lInputForward = new Vector3(mMovementX, 0f, mMovementY); // We do the inverse tilt so we calculate the rotation in "natural up" space vs. "actor up" space. Quaternion lInvTilt = QuaternionExt.FromToRotation(_Transform.up, Vector3.up); // Camera forward in "natural up" Vector3 lCameraForward = lInvTilt * mMotionController._CameraTransform.forward; // Create a quaternion that gets us from our world-forward to our camera direction. Quaternion lToCamera = Quaternion.LookRotation(lCameraForward, lInvTilt * _Transform.up); // Transform joystick from world space to camera space. Now the input is relative // to how the camera is facing. Vector3 lMoveDirection = lToCamera * lInputForward; mInputFromCameraAngle = NumberHelper.GetHorizontalAngle(lCameraForward, lMoveDirection); } }
public static Vector3 CalculateShotingDir(int seed, float yaw, float pitch, float spreadX, float spreadY) { Quaternion q = Quaternion.Euler(pitch, yaw, 0); Vector3 forward = q.Forward(); Vector3 right = q.Right(); Vector3 up = q.Up(); float x; float y; //,z; x = (float)UniformRandom.RandomFloat(seed + 0, -0.5, 0.5) + (float)UniformRandom.RandomFloat(seed + 1, -0.5, 0.5); y = (float)UniformRandom.RandomFloat(seed + 2, -0.5, 0.5) + (float)UniformRandom.RandomFloat(seed + 3, -0.5, 0.5); //z = x * x + y * y; float res1 = spreadX * x; float res2 = spreadY * y; right = Vector3Ext.Scale(right, res1); up = Vector3Ext.Scale(up, res2); var newForward = Vector3Ext.Add(forward, right); newForward = Vector3Ext.Add(newForward, up); //_logger.InfoFormat("spreadX {0}, spreadY {1}, x {2} y {3}, right{4}, up{5}", spreadX, spreadY, x, y, right, up); return(newForward); }
/// <summary> /// Allows the motor to process after the camera and controller have completed /// </summary> public override void PostRigLateUpdate() { base.PostRigLateUpdate(); // Determine if we rotate the anchor to match our rotation if (_RotateAnchor && Anchor != null) { if (_RotateAnchorAlias.Length == 0 || RigController.InputSource == null || RigController.InputSource.IsPressed(_RotateAnchorAlias)) { if (mCharacterController != null) { float lToCameraAngle = Vector3Ext.HorizontalAngleTo(Anchor.forward, RigController.Transform.forward, Anchor.up); Quaternion lRotation = Quaternion.AngleAxis(lToCameraAngle, Vector3.up); mCharacterController.Yaw = mCharacterController.Yaw * lRotation; Anchor.rotation = mCharacterController.Tilt * mCharacterController.Yaw; } else { float lToCameraAngle = Vector3Ext.HorizontalAngleTo(Anchor.forward, RigController.Transform.forward, Anchor.up); Quaternion lRotation = Quaternion.AngleAxis(lToCameraAngle, Vector3.up); Anchor.rotation = Anchor.rotation * lRotation; } } } }
/// <summary> /// Calculate how much to move an rotate by /// </summary> /// <param name="rMove"></param> /// <param name="rRotate"></param> protected virtual void CalculateMove(Vector3 rWaypoint, ref Vector3 rMove, ref Quaternion rRotate) { float lDeltaTime = TimeManager.SmoothedDeltaTime; // Direction we need to travel in Vector3 lDirection = rWaypoint - transform.position; lDirection.y = lDirection.y - _PathHeight; lDirection.Normalize(); // Determine our rotation Vector3 lVerticalDirection = Vector3.Project(lDirection, transform.up); Vector3 lLateralDirection = lDirection - lVerticalDirection; float lYawAngle = Vector3Ext.SignedAngle(transform.forward, lLateralDirection); if (_RotationSpeed == 0f) { rRotate = Quaternion.AngleAxis(lYawAngle, transform.up); } else { rRotate = Quaternion.AngleAxis(Mathf.Sign(lYawAngle) * Mathf.Min(Mathf.Abs(lYawAngle), _RotationSpeed * lDeltaTime), transform.up); } // Determine the movement if (_UseNavMeshPosition) { rMove = mNavMeshAgent.nextPosition - transform.position; } // In this case, we'll calculate movement ourselves else { // Grab the base movement speed float lMoveSpeed = mRootMotionMovement.magnitude / lDeltaTime; if (lMoveSpeed == 0f) { lMoveSpeed = _MovementSpeed; } // Calculate our own slowing float lRelativeMoveSpeed = 1f; if (mIsInSlowDistance && _SlowFactor > 0f) { float lSlowPercent = (mTargetDistance - _StopDistance) / (_SlowDistance - _StopDistance); lRelativeMoveSpeed = ((1f - _SlowFactor) * lSlowPercent) + _SlowFactor; } // TRT 4/5/2016: Force the slow distance as an absolute value if (mIsInSlowDistance && _SlowFactor > 0f) { lMoveSpeed = _SlowFactor; lRelativeMoveSpeed = 1f; } // Set the final velocity based on the future rotation Quaternion lFutureRotation = transform.rotation * rRotate; rMove = lFutureRotation.Forward() * (lMoveSpeed * lRelativeMoveSpeed * lDeltaTime); } }
/// <summary> /// Allows the motor to process after the camera and controller have completed /// </summary> public override void PostRigLateUpdate() { base.PostRigLateUpdate(); Transform lAnchorTransform = Anchor; // If the anchor updates its rotation based on the rig, we need to get the final position of the rig if (_IsActorMatchingRotation && (RigController.ActiveMotor == this)) { RigController._Transform.position = lAnchorTransform.position + (lAnchorTransform.rotation * AnchorOffset) + (lAnchorTransform.rotation * _Offset); RigController._Transform.rotation = Quaternion.Euler(RigController._Transform.rotation.eulerAngles.x, lAnchorTransform.rotation.eulerAngles.y, 0f); } if (_RotateAnchor && lAnchorTransform != null) { bool lIsRotatingAnchor = _RotateAnchorAlias.Length == 0 || RigController.InputSource == null || RigController.InputSource.IsPressed(_RotateAnchorAlias); if (lIsRotatingAnchor || mWasRotatingAnchor) { if (mCharacterController != null) { float lToCameraAngle = Vector3Ext.HorizontalAngleTo(lAnchorTransform.forward, RigController.Transform.forward, lAnchorTransform.up); Quaternion lRotation = Quaternion.AngleAxis(lToCameraAngle, Vector3.up); mCharacterController.Yaw = mCharacterController.Yaw * lRotation; lAnchorTransform.rotation = mCharacterController.Tilt * mCharacterController.Yaw; } else { float lToCameraAngle = Vector3Ext.HorizontalAngleTo(lAnchorTransform.forward, RigController.Transform.forward, lAnchorTransform.up); Quaternion lRotation = Quaternion.AngleAxis(lToCameraAngle, Vector3.up); lAnchorTransform.rotation = lAnchorTransform.rotation * lRotation; } mAnchorLastRotation = lAnchorTransform.rotation; // Since we are no longer rotating, we need to clear our out values if (!lIsRotatingAnchor) { _Euler.y = LocalYaw; _Euler.x = LocalPitch; _EulerTarget = _Euler; mViewVelocityY = 0f; mViewVelocityX = 0f; mWasRotatingAnchor = false; } } } else { mWasRotatingAnchor = false; } //Debug.Log("Char Yaw:" + RigController.Anchor.rotation.eulerAngles.y.ToString("f5") + " Anchor Yaw:" + Anchor.rotation.eulerAngles.y.ToString("f5") + " Cam Yaw:" + RigController._Transform.eulerAngles.y.ToString("f5")); }
/// <summary> /// Look at the incoming message to determine if it means we should react /// </summary> /// <param name="rMessage"></param> public override void OnMessageReceived(IMessage rMessage) { if (rMessage == null) { return; } if (rMessage.IsHandled) { return; } if (mActorController.State.Stance != EnumControllerStance.TRAVERSAL) { return; } if (rMessage is CombatMessage) { CombatMessage lCombatMessage = rMessage as CombatMessage; // Attack messages if (lCombatMessage.Attacker == mMotionController.gameObject) { } // Defender messages else if (lCombatMessage.Defender == mMotionController.gameObject) { if (rMessage.ID == CombatMessage.MSG_DEFENDER_DAMAGED) { if (!mIsActive) { Vector3 lLocalPosition = mMotionController._Transform.InverseTransformPoint(lCombatMessage.HitPoint); Vector3 lLocalDirection = (lLocalPosition - Vector3.zero).normalized; float lAttackAngle = Vector3Ext.HorizontalAngleTo(Vector3.forward, lLocalDirection, Vector3.up); mMotionController.ActivateMotion(this, (int)lAttackAngle); } else { mMotionController.SetAnimatorMotionPhase(mMotionLayer._AnimatorLayerIndex, PHASE_START, Parameter, true); } rMessage.IsHandled = true; } } } else if (rMessage is DamageMessage) { if (rMessage.ID == CombatMessage.MSG_DEFENDER_DAMAGED) { mMotionController.ActivateMotion(this, 0); rMessage.IsHandled = true; } } }
/// <summary> /// Rotate to the specified target's position over time /// </summary> /// <param name="rTarget">Transform we are rotating to</param> /// <param name="rSpeed">Degrees per second to rotate</param> /// <param name="rDeltaTime">Current delta time</param> /// <param name="rRotation">Resulting delta rotation needed to get to the target</param> protected void RotateToTarget(Transform rTarget, float rSpeed, float rDeltaTime, ref Quaternion rRotation) { Vector3 lNewPosition = mMotionController._Transform.position + (mMotionController._Transform.rotation * mMotionController.RootMotionMovement); Vector3 lToTarget = rTarget.position - lNewPosition; float lSpeed = (rSpeed > 0f ? rSpeed : _ToTargetRotationSpeed); float lToAnchorAngle = Vector3Ext.HorizontalAngleTo(mMotionController._Transform.forward, lToTarget.normalized, mMotionController._Transform.up); lToAnchorAngle = Mathf.Sign(lToAnchorAngle) * Mathf.Min(lSpeed * rDeltaTime, Mathf.Abs(lToAnchorAngle)); rRotation = Quaternion.AngleAxis(lToAnchorAngle, Vector3.up); }
// ************************************** EDITOR SUPPORT ************************************** /// <summary> /// Allow the constraint to render it's own GUI /// </summary> /// <returns>Reports if the object's value was changed</returns> public override bool OnInspectorConstraintGUI(bool rIsSelected) { bool lIsDirty = false; #if UNITY_EDITOR GUILayout.Space(5); // Determine if the swing is changing if (mBone != null) { Vector3 lSwing = _Swing.eulerAngles; Vector3 lNewSwing = InspectorHelper.Vector3Fields("Swing", "Euler angles to swing the bone.", lSwing, true, true, false); if (lNewSwing != lSwing) { lIsDirty = true; // Grab the amount that was just rotated by based on the current rotation. // We do this so the change is relative to the current swing rotation Vector3 lDeltaRotations = lNewSwing - lSwing; _Swing = _Swing * Quaternion.Euler(lDeltaRotations); } // Determine if the twist is changing float lTwist = Vector3Ext.SignedAngle(Vector3.up, _Twist * Vector3.up, Vector3.forward); float lNewTwist = EditorGUILayout.FloatField("Twist", lTwist); if (lNewTwist != lTwist) { lIsDirty = true; _Twist = Quaternion.AngleAxis(lNewTwist, Vector3.forward); } // Reset the values if needed if (GUILayout.Button("reset rotation", EditorStyles.miniButton)) { _Swing = Quaternion.identity; _Twist = Quaternion.identity; lIsDirty = true; } if (lIsDirty) { mBone.SetLocalRotation(_Swing, _Twist, 1f); } } #endif return(lIsDirty); }
/// <summary> /// This function renders out the handles that allow us to edit the twist limits. It /// isn't actually meant to change the twist itself /// </summary> /// <param name="rBone"></param> /// <param name="rMinAngle"></param> /// <param name="rMaxAngle"></param> /// <returns></returns> public static bool JointTwistLimitsHandle(BoneControllerBone rBone, ref float rMinAngle, ref float rMaxAngle) { bool lIsDirty = false; #if UNITY_EDITOR Vector3 lWorldPosition = rBone._Transform.position; Quaternion lWorldSwingRotation = rBone.WorldBindRotation * rBone._Swing; Color lGUIColor = GUI.color; Color lHandleColor = Handles.color; float lHandleScale = 0.2f; if (rBone.Skeleton.EditorAutoScaleHandles) { lHandleScale = HandleUtility.GetHandleSize(rBone.Transform.position) * HandlesHelper.HandleScale; } // Render the border of the angles float lTwistAngle = Vector3Ext.SignedAngle(Vector3.up, rBone._Twist * Vector3.up, Vector3.forward); Quaternion lActualRotation = Quaternion.AngleAxis(lTwistAngle, Vector3.forward); Quaternion lMinRotation = Quaternion.AngleAxis(rMinAngle, Vector3.forward); Quaternion lMaxRotation = Quaternion.AngleAxis(rMaxAngle, Vector3.forward); Vector3 lMinOffset = lWorldSwingRotation * lMinRotation * Vector3.up * lHandleScale; Vector3 lMaxOffset = lWorldSwingRotation * lMaxRotation * Vector3.up * lHandleScale; Handles.color = new Color(0.1f, 0.1f, 0.6f, 1f); Handles.DrawLine(lWorldPosition, lWorldPosition + lMinOffset); Handles.DrawLine(lWorldPosition, lWorldPosition + lMaxOffset); //Handles.DrawLine(lWorldPosition, lWorldPosition + (lWorldSwingRotation * rBone.Twist * Vector3.up * (lHandleScale * 1.1f))); // Render the solid of the angles Handles.color = new Color(0.25f, 0.60f, 0.95f, 0.1f); Handles.DrawSolidArc(lWorldPosition, rBone.Transform.rotation * rBone._BoneForward, lWorldSwingRotation * lMinRotation * Vector3.up, Mathf.Abs(rMinAngle) + rMaxAngle, lHandleScale); // Render text GUI.color = new Color(0.05f, 0.05f, 0.5f, 1f); Handles.Label(lWorldPosition + lMinOffset, "min:\r\n" + rMinAngle.ToString("0.00")); Handles.Label(lWorldPosition + lMaxOffset, "max:\r\n" + rMaxAngle.ToString("0.00")); Handles.Label(lWorldPosition + (lWorldSwingRotation * lActualRotation * Vector3.up * (lHandleScale * 1.3f)), " " + lTwistAngle.ToString("0.00")); // Reset GUI.color = lGUIColor; Handles.color = lHandleColor; #endif return lIsDirty; }
/// <summary> /// This function renders out the handles that allow us to edit the twist limits. It /// isn't actually meant to change the twist itself /// </summary> /// <param name="rBone"></param> /// <param name="rMinAngle"></param> /// <param name="rMaxAngle"></param> /// <returns></returns> public static bool JointSwingAxisLimitsHandle(BoneControllerBone rBone, Vector3 rAxis, float rMinAngle, float rMaxAngle) { bool lIsDirty = false; #if UNITY_EDITOR Vector3 lWorldPosition = rBone._Transform.position; Quaternion lWorldSwing = rBone.WorldBindRotation * rBone.Swing; Color lGUIColor = GUI.color; Color lHandleColor = Handles.color; float lHandleScale = 0.2f; if (rBone.Skeleton.EditorAutoScaleHandles) { lHandleScale = HandleUtility.GetHandleSize(rBone.Transform.position) * HandlesHelper.HandleScale; } // Render the border of the angles Quaternion lMinRotation = Quaternion.AngleAxis(rMinAngle, rAxis); Quaternion lMaxRotation = Quaternion.AngleAxis(rMaxAngle, rAxis); // We don't use the world swing since we want the rotation based on the bind position Vector3 lMinOffset = rBone.WorldBindRotation * lMinRotation * Vector3.forward * lHandleScale; Vector3 lMaxOffset = rBone.WorldBindRotation * lMaxRotation * Vector3.forward * lHandleScale; Handles.color = new Color(0.94118f, 0.39608f, 0.13333f, 1f); Handles.DrawLine(lWorldPosition, lWorldPosition + lMinOffset); Handles.DrawLine(lWorldPosition, lWorldPosition + lMaxOffset); // Render the solid of the angles Handles.color = new Color(0.94118f, 0.39608f, 0.13333f, 0.1f); Handles.DrawSolidArc(lWorldPosition, rBone.WorldBindRotation * rAxis, (rBone.WorldBindRotation * lMinRotation * Vector3.forward), Mathf.Abs(rMinAngle) + rMaxAngle, lHandleScale); // Draw text GUI.color = new Color(0.72549f, 0.30588f, 0.10588f, 1f); Handles.Label(lWorldPosition + lMinOffset, "min:\r\n" + rMinAngle.ToString("0.00")); Handles.Label(lWorldPosition + lMaxOffset, "max:\r\n" + rMaxAngle.ToString("0.00")); Vector3 lDirectionAxis = Vector3.Cross(rAxis, rBone._BindRotation * rBone._BoneForward); float lSwingAngle = Vector3Ext.SignedAngle(lDirectionAxis, rBone.Swing * lDirectionAxis, rAxis); Handles.Label(lWorldPosition + (lWorldSwing * (Vector3.forward * (lHandleScale * 1.3f))), " " + lSwingAngle.ToString("0.00")); // Reset GUI.color = lGUIColor; Handles.color = lHandleColor; #endif return lIsDirty; }
public bool Find(GameObject target, bool showLines = false) { if (target == null || owner == null) { return(false); } bool result = false; Vector3 origin = owner.position; float angle = SetAimDirection(-owner.right); for (int i = 0; i <= rayCount; i++) { RaycastHit2D[] hits = Physics2D.RaycastAll(origin, Vector3Ext.GetVectorFromAngle(angle), viewDistance, mask); if (hits.Any()) { foreach (RaycastHit2D hit in hits) { if (hit.collider.gameObject == self) { continue; } Color c = Color.green; if (target != null && hit.collider.gameObject == target) { c = Color.red; result = true; } if (showLines) { Debug.DrawLine(origin, hit.point, c, Time.deltaTime); } break; } } else if (showLines) { Debug.DrawLine(origin, origin + Vector3Ext.GetVectorFromAngle(angle) * viewDistance, Color.green, Time.deltaTime); } angle -= angleIncrease; } return(result); }
/// <summary> /// Allow the joint to render it's own GUI. This GUI is used /// for displaying and manipulating the joint itself. /// </summary> /// <returns>Reports if the object's value was changed</returns> public override bool OnInspectorManipulatorGUI(IKBoneModifier rModifier) { #if UNITY_EDITOR // Determine if the swing is changing if (mBone != null) { // Determine if the swing is changing Vector3 lSwing = rModifier.Swing.eulerAngles; Vector3 lNewSwing = InspectorHelper.Vector3Fields("Swing", "Euler angles to swing the bone.", lSwing, true, true, false); //lBoneForward.x == 0f, lBoneForward.y == 0f, lBoneForward.z == 0f); if (lNewSwing != lSwing) { // Grab the amount that was just rotated by based on the current rotation. // We do this so the change is relative to the current swing rotation Vector3 lDeltaRotations = lNewSwing - lSwing; rModifier.Swing = rModifier.Swing * Quaternion.Euler(lDeltaRotations); rModifier.IsDirty = true; } float lTwist = Vector3Ext.SignedAngle(Vector3.up, rModifier.Twist * Vector3.up, Vector3.forward); float lNewTwist = EditorGUILayout.FloatField("Twist", lTwist); if (lNewTwist != lTwist) { rModifier.Twist = Quaternion.AngleAxis(lNewTwist, Vector3.forward); rModifier.IsDirty = true; } // Reset the values if needed if (GUILayout.Button("reset rotation", EditorStyles.miniButton)) { rModifier.Swing = Quaternion.identity; rModifier.Twist = Quaternion.identity; rModifier.IsDirty = true; mBone._Transform.localRotation = mBone._BindRotation; } if (rModifier.IsDirty) { ApplyLimits(ref rModifier.Swing, ref rModifier.Twist); } } #endif return(rModifier.IsDirty); }
/// <summary> /// Rotates the body to point towards the cross hairs /// </summary> protected void RotateChestToTargetForward(Vector3 rTarget, float rWeight) { if (rWeight == 0f) { return; } float lHAngle = 0f; float lVAngle = 0f; Transform lBody = mMotionController._Transform; Transform lCamera = mMotionController._CameraTransform; Transform lSpine = mMotionController.Animator.GetBoneTransform(HumanBodyBones.Spine); Transform lChest = mMotionController.Animator.GetBoneTransform(HumanBodyBones.Chest); // The target direction helps determine how we'll rotate the arm Vector3 lTargetDirection = (lCamera != null ? lCamera.forward : rTarget); lHAngle = _HorizontalAimAngle * rWeight; lVAngle = (_VerticalAimAngle + Vector3Ext.HorizontalAngleTo(lBody.forward, lTargetDirection, lBody.right)) * rWeight; lVAngle = Mathf.Clamp(lVAngle, -65f, 65f); if (lSpine != null && lChest != null) { lHAngle = lHAngle * 0.5f; } if (lSpine != null && lChest != null) { lVAngle = lVAngle * 0.5f; } if (lSpine != null) { lSpine.rotation = Quaternion.AngleAxis(lHAngle, lBody.up) * Quaternion.AngleAxis(lVAngle, lBody.right) * lSpine.rotation; } if (lChest != null) { lChest.rotation = Quaternion.AngleAxis(lHAngle, lBody.up) * Quaternion.AngleAxis(lVAngle, lBody.right) * lChest.rotation; } }
/// <summary> /// Calculate how much to move an rotate by /// </summary> /// <param name="rMove"></param> /// <param name="rRotate"></param> protected virtual void CalculateMove(Vector3 rWaypoint, ref Vector3 rMove, ref Quaternion rRotate) { float lDeltaTime = TimeManager.SmoothedDeltaTime; // Direction we need to travel in Vector3 lDirection = rWaypoint - transform.position; lDirection.Normalize(); // Determine our rotation Vector3 lVerticalDirection = Vector3.Project(lDirection, transform.up); Vector3 lLateralDirection = lDirection - lVerticalDirection; float lYawAngle = Vector3Ext.SignedAngle(transform.forward, lLateralDirection); if (_RotationSpeed == 0f) { rRotate = Quaternion.AngleAxis(lYawAngle, transform.up); } else { rRotate = Quaternion.AngleAxis(Mathf.Sign(lYawAngle) * Mathf.Min(Mathf.Abs(lYawAngle), _RotationSpeed * lDeltaTime), transform.up); } // Determine the movement float lMoveSpeed = _FastSpeed; if (mIsInSlowDistance && _SlowSpeed > 0f) { lMoveSpeed = _SlowSpeed; } // Set the final velocity based on the future rotation Quaternion lFutureRotation = transform.rotation * rRotate; rMove = lFutureRotation.Forward() * (lMoveSpeed * lDeltaTime); }
public float SetAimDirection(Vector3 dir) { return(Vector3Ext.GetAngleFromVector(dir) - fov / 2f); }
/// <summary> /// Converts the nav mesh agent data into psuedo-input that the motion controller /// will use to drive animations. /// </summary> protected void SimulateInput() { float lDeltaTime = TimeManager.SmoothedDeltaTime; // Direction we need to travel in Vector3 lDirection = mWaypointVector; lDirection.y = lDirection.y - _PathHeight; lDirection.Normalize(); // Determine our view Vector3 lVerticalDirection = Vector3.Project(lDirection, _Transform.up); Vector3 lLateralDirection = lDirection - lVerticalDirection; mInputFromAvatarAngle = Vector3Ext.SignedAngle(_Transform.forward, lLateralDirection); // Determine how much we simulate the view x. We temper it to make it smooth float lYawAngleAbs = Mathf.Min(Mathf.Abs(mInputFromAvatarAngle * lDeltaTime), mViewSpeedPer60FPSTick * lDeltaTime); if (lYawAngleAbs < EPSILON) { lYawAngleAbs = 0f; } mViewX = Mathf.Sign(mInputFromAvatarAngle) * lYawAngleAbs; // Determine our movement if (mTargetDistance > _StopDistance) { // Calculate our own slowing float lRelativeMoveSpeed = _NormalizedSpeed; if (mIsInSlowDistance && _SlowFactor > 0f) { float lSlowPercent = (mTargetDistance - _StopDistance) / (_SlowDistance - _StopDistance); lRelativeMoveSpeed = ((1f - _SlowFactor) * lSlowPercent) + _SlowFactor; } // TRT 4/5/2016: Force the slow distance as an absolute value if (mIsInSlowDistance && _SlowFactor > 0f) { lRelativeMoveSpeed = _SlowFactor; } mMovementY = 1f * lRelativeMoveSpeed; mMotionController.TargetNormalizedSpeed = lRelativeMoveSpeed; } // Grab extra input information if we can if (mMotionController._CameraTransform == null) { mInputFromCameraAngle = mInputFromAvatarAngle; } else { Vector3 lInputForward = new Vector3(mMovementX, 0f, mMovementY); // We do the inverse tilt so we calculate the rotation in "natural up" space vs. "actor up" space. Quaternion lInvTilt = QuaternionExt.FromToRotation(_Transform.up, Vector3.up); // Camera forward in "natural up" Vector3 lCameraForward = lInvTilt * mMotionController._CameraTransform.forward; // Create a quaternion that gets us from our world-forward to our camera direction. Quaternion lToCamera = Quaternion.LookRotation(lCameraForward, lInvTilt * _Transform.up); // Transform joystick from world space to camera space. Now the input is relative // to how the camera is facing. Vector3 lMoveDirection = lToCamera * lInputForward; mInputFromCameraAngle = NumberHelper.GetHorizontalAngle(lCameraForward, lMoveDirection); } }
/// <summary> /// Core function that relies on the law of cosines in order to /// determine the angles between two bones. /// </summary> /// <param name="rState">State object containing information about what is to be solved and the results</param> public static void SolveIK(ref IKSolverState rState, float rBone2Extension = 0f) { if (rState.Bones == null || rState.Bones.Count != 2) { return; } // Extract out the data BoneControllerBone lBone1 = rState.Bones[0]; BoneControllerBone lBone2 = rState.Bones[1]; // Grab basic bone info. We need the end's bind rotation so that it will keep the cosine equations // on a single plane after limits are processed. float lBone1Length = Vector3.Distance(lBone1.Transform.position, lBone2.Transform.position); Vector3 lBone1Position = lBone1.Transform.position; Quaternion lBone1Rotation = lBone1.Transform.rotation; Vector3 lBone1BendAxis = rState.BoneBendAxes[0]; float lBone2Length = lBone2.Length + rBone2Extension; Vector3 lBone2Position = lBone2.Transform.position; Quaternion lBone2Rotation = lBone2.Transform.rotation; Vector3 lBone2BendAxis = rState.BoneBendAxes[1]; Vector3 lBone3Position = lBone2Position + (lBone2Rotation * lBone2.BindRotation * lBone2.ToBoneForward * (Vector3.forward * lBone2Length)); // Check if our final position is too far. If so, we need to bring it in Vector3 lTargetPosition = rState.TargetPosition; float lBone1ToTargetLength = Vector3.Distance(lBone1Position, lTargetPosition); if (lBone1ToTargetLength > lBone1Length + lBone2Length) { // We remove a tiny bit of length so we never end up with a // bone angle of 0. This allows us to account for the bend axis. lBone1ToTargetLength = lBone1Length + lBone2Length;// -0.0000f; Vector3 lDirection = (lTargetPosition - lBone1Position).normalized; lTargetPosition = lBone1Position + (lDirection * lBone1ToTargetLength); } // Grab the angle between the target vector and the mid bone. Then, create the final rotation vector for the first bone float lAngle = (-(lBone2Length * lBone2Length) + (lBone1Length * lBone1Length) + (lBone1ToTargetLength * lBone1ToTargetLength)) / (2f * lBone1Length * lBone1ToTargetLength); float lBone1Angle = Mathf.Acos(Mathf.Clamp(lAngle, -1f, 1f)) * Mathf.Rad2Deg; // The bind rotation in world coordinates Quaternion lBaseRootRotation = (rState.UseBindRotation ? lBone1.WorldBindRotation : lBone1.Transform.rotation * lBone1.ToBoneForward); // Grab the rotation that gets us from the base vector to the target vector. This is the hypotenuse. Quaternion lToTargetRotation = Quaternion.FromToRotation(lBaseRootRotation * Vector3.forward, lTargetPosition - lBone1Position); // Determine the axis we'll rotate the root bone around Vector3 lRootBendAxis = Vector3.zero; if (rState.UsePlaneNormal) { lRootBendAxis = Vector3Ext.PlaneNormal(lBone1Position, lBone2Position, lBone3Position); } else { lRootBendAxis = lToTargetRotation * lBaseRootRotation * lBone1BendAxis; } // Rotate from the base rotation to the target rotation and finally to the correct rotation (based on the angle) lBone1Rotation = Quaternion.AngleAxis(lBone1Angle, lRootBendAxis) * lToTargetRotation * lBaseRootRotation; // Now we can determine the position of the second bone lBone2Position = lBone1Position + (lBone1Rotation * (Vector3.forward * lBone1Length)); // Want to ensure we don't end up with a '0' look direction. Otherwise, we'll get infinite errors. if (Vector3.SqrMagnitude(lTargetPosition - lBone2Position) > 0.001f) { // Grabbing the rotation of the second bone is easier since we just look at the target Vector3 lForward = lTargetPosition - lBone2Position; Vector3 lRight = lBone1Rotation * lBone2BendAxis; Vector3 lUp = Vector3.Cross(lForward, lRight).normalized; lBone2Rotation = Quaternion.LookRotation(lForward, lUp); } // Return the results rState.Rotations.Clear(); rState.AddRotation(lBone1, lBone1Rotation); rState.AddRotation(lBone2, lBone2Rotation); // Set the position valudes (for debugging) rState.BonePositions.Clear(); rState.BonePositions.Add(lBone1Position); rState.BonePositions.Add(lBone2Position); rState.BonePositions.Add(lBone2Position + (lBone2Rotation * (Vector3.forward * lBone2Length))); // Debug if (rState.IsDebugEnabled) { DebugDraw.DrawOctahedronOverlay(lBone1Position, Quaternion.identity, 0.03f, Color.red, 1f); DebugDraw.DrawOctahedronOverlay(lBone2Position, Quaternion.identity, 0.03f, Color.green, 1f); DebugDraw.DrawOctahedronOverlay(lBone3Position, Quaternion.identity, 0.03f, Color.blue, 1f); DebugDraw.DrawOctahedronOverlay(lTargetPosition, Quaternion.identity, 0.03f, Color.magenta, 1f); DebugDraw.DrawLineOverlay(lBone1Position, lBone1Position + (lBone1Rotation * (Vector3.forward * 0.5f)), 0.01f, Color.blue, 0.75f); DebugDraw.DrawLineOverlay(lBone1Position, lBone1Position + (lBone1Rotation * (Vector3.up * 0.5f)), 0.01f, Color.green, 0.75f); DebugDraw.DrawLineOverlay(lBone1Position, lBone1Position + (lBone1Rotation * (Vector3.right * 0.5f)), 0.01f, Color.red, 0.75f); DebugDraw.DrawLineOverlay(lBone2Position, lBone2Position + (lBone2Rotation * Vector3.forward), 0.02f, Color.blue, 0.5f); DebugDraw.DrawLineOverlay(lBone2Position, lBone2Position + (lBone2Rotation * Vector3.up), 0.02f, Color.green, 0.5f); DebugDraw.DrawLineOverlay(lBone2Position, lBone2Position + (lBone2Rotation * Vector3.right), 0.02f, Color.red, 0.5f); } }
/// <summary> /// Updates the motor over time. This is called by the controller /// every update cycle so movement can be updated. /// </summary> /// <param name="rDeltaTime">Time since the last frame (or fixed update call)</param> /// <param name="rUpdateIndex">Index of the update to help manage dynamic/fixed updates. [0: Invalid update, >=1: Valid update]</param> /// <param name="rTiltAngle">Amount of tilting the camera needs to do to match the anchor</param> public override CameraTransform RigLateUpdate(float rDeltaTime, int rUpdateIndex, float rTiltAngle = 0f) { Transform lAnchorTransform = Anchor; if (lAnchorTransform == null) { return(mRigTransform); } if (RigController == null) { return(mRigTransform); } Transform lCameraTransform = RigController._Transform; // Determine how much the anchor's yaw changed (we want to stay relative to the anchor direction) float lAnchorRootYawDelta = Vector3Ext.HorizontalAngleTo(mAnchorLastRotation.Forward(), lAnchorTransform.forward, lAnchorTransform.up); float lAnchorYaw = (Mathf.Abs(rTiltAngle) >= 2f ? lAnchorRootYawDelta : 0f); if (!RigController.RotateAnchorOffset) { lAnchorRootYawDelta = 0f; lAnchorYaw = 0f; } // Grab any euler changes this frame mFrameEuler = GetFrameEuler(true, true); // RigController.RotateAnchorOffset); // Get our predicted based on the frame and anchor changes. We want this to get the right-vector Quaternion lNewCameraRotation = Quaternion.AngleAxis(mFrameEuler.y + lAnchorYaw, (RigController.RotateAnchorOffset ? RigController.Tilt.Up() : Vector3.up)) * lCameraTransform.rotation; // Now grab the full (vertical + horizontal) position of our final focus. //Vector3 lNewFocusPosition = lAnchorTransform.position + (lAnchorTransform.up * lOffset.y); //lNewFocusPosition = lNewFocusPosition + (lNewCameraRotation.Right() * lOffset.x); Vector3 lNewFocusPosition = GetFocusPosition(lNewCameraRotation); // Default the value Vector3 lNewCameraPosition = lCameraTransform.position; Vector3 lToFocusPosition = lCameraTransform.forward; // If we're tilting, act like a fixed if (RigController.FrameForceToFollowAnchor || Mathf.Abs(rTiltAngle) >= 2f) { // Get the local position and the right vector of the camera relative to the last frame Matrix4x4 lOldFocusMatrix = Matrix4x4.TRS(mFocusLastPosition, (RigController.RotateAnchorOffset ? mAnchorLastRotation : Quaternion.identity), Vector3.one); Matrix4x4 lNewFocusMatrix = Matrix4x4.TRS(lNewFocusPosition, (RigController.RotateAnchorOffset ? lAnchorTransform.rotation : Quaternion.identity), Vector3.one); // The matrix will add our anchor delta. But, we also added it when we use the LocalYaw // We'll remove it so we don't double up. if (mTargetYaw < float.MaxValue) { mFrameEuler.y = mFrameEuler.y - lAnchorRootYawDelta; } // If nothing has changed, we won't update. This is important due to the fact // that the inverse of the matix causes a small floating point error to move us. if (mFrameEuler.sqrMagnitude != 0f || lOldFocusMatrix != lNewFocusMatrix) { Vector3 lLocalPosition = lOldFocusMatrix.inverse.MultiplyPoint(lCameraTransform.position); Vector3 lLocalCameraRight = lOldFocusMatrix.inverse.MultiplyVector(lCameraTransform.right); // Rotate the old local position based on the frame's rotation changes Quaternion lLocalRotation = Quaternion.AngleAxis(mFrameEuler.y, Vector3.up) * Quaternion.AngleAxis(mFrameEuler.x, lLocalCameraRight); lLocalPosition = lLocalRotation * lLocalPosition; // Grab the new position based on the updated matrix lNewCameraPosition = lNewFocusMatrix.MultiplyPoint(lLocalPosition); } } // If we have a target forward, act like a fixed else if (mTargetForward.sqrMagnitude > 0f) { lNewCameraRotation = lNewCameraRotation * Quaternion.AngleAxis(mFrameEuler.x, Vector3.right); lNewCameraPosition = lNewFocusPosition - (lNewCameraRotation.Forward() * mDistance); } else { lNewCameraRotation = lNewCameraRotation * Quaternion.AngleAxis(mFrameEuler.x, Vector3.right); Vector3 lOldCameraPosition = mFocusLastPosition - (lNewCameraRotation.Forward() * mDistance); // Grab the new focus position using the drag rotation we get lToFocusPosition = lNewFocusPosition - lOldCameraPosition; if (lToFocusPosition.sqrMagnitude < 0.0001f) { lToFocusPosition = lCameraTransform.forward; } lNewCameraRotation = Quaternion.LookRotation(lToFocusPosition.normalized, (RigController.RotateAnchorOffset ? lAnchorTransform.up : Vector3.up)); //lNewFocusPosition = lAnchorTransform.position + (lAnchorTransform.up * lOffset.y); //lNewFocusPosition = lNewFocusPosition + (lNewCameraRotation.Right() * lOffset.x); lNewFocusPosition = GetFocusPosition(lNewCameraRotation); // If something is between the new focus position and the anchor, the new focus position is the anchor //Color lHitColor = Color.black; //RaycastHit lFocusHit; Vector3 lAnchorPosition = Vector3.zero; if (RigController.RotateAnchorOffset) { lAnchorPosition = lAnchorTransform.position + (lAnchorTransform.rotation * AnchorOffset) + (lAnchorTransform.up * _Offset.y); } else { lAnchorPosition = lAnchorTransform.position + AnchorOffset + (Vector3.up * _Offset.y); } lToFocusPosition = lNewFocusPosition - lAnchorPosition; // Get the drag direction and pull out the horizontal component lToFocusPosition = lNewFocusPosition - lOldCameraPosition; if (lToFocusPosition.sqrMagnitude < 0.0001f) { lToFocusPosition = lCameraTransform.forward; } Vector3 lToFocusPositionVertical = Vector3.Project(lToFocusPosition, (RigController.RotateAnchorOffset ? RigController.Tilt.Up() : Vector3.up)); Vector3 lToFocusPositionHorizontal = lToFocusPosition - lToFocusPositionVertical; // Get the pitch rotation regardless of our tilt lNewCameraRotation = lCameraTransform.rotation * Quaternion.AngleAxis(mFrameEuler.x, Vector3.right); Vector3 lRotationEuler = (RigController.RotateAnchorOffset ? Quaternion.Inverse(RigController.Tilt) * lNewCameraRotation : lNewCameraRotation).eulerAngles; // Add the pitch to our look-at rotation Quaternion lNewAnchorRotation = Quaternion.LookRotation(lToFocusPositionHorizontal, (RigController.RotateAnchorOffset ? RigController.Tilt.Up() : Vector3.up)) * Quaternion.Euler(lRotationEuler.x, 0f, 0f); // Using the rotation, grab the new camera position lNewCameraPosition = lNewFocusPosition - (lNewAnchorRotation.Forward() * mDistance); } // Determine our rotation lToFocusPosition = lNewFocusPosition - lNewCameraPosition; if (lToFocusPosition.sqrMagnitude < 0.0001f) { lToFocusPosition = lCameraTransform.forward; } lNewCameraRotation = Quaternion.LookRotation(lToFocusPosition.normalized, (RigController.RotateAnchorOffset ? lAnchorTransform.up : Vector3.up)); // We have to do a final check if we have exceeded rotation limits Quaternion lNewLocalCameraRotation = (RigController.RotateAnchorOffset ? Quaternion.Inverse(Anchor.transform.rotation) : Quaternion.identity) * lNewCameraRotation; Vector3 lNewLocalEuler = lNewLocalCameraRotation.eulerAngles; if (lNewLocalEuler.y > 180f) { lNewLocalEuler.y = lNewLocalEuler.y - 360f; } else if (lNewLocalEuler.y < -180f) { lNewLocalEuler.y = lNewLocalEuler.y + 360f; } if (lNewLocalEuler.x > 180f) { lNewLocalEuler.x = lNewLocalEuler.x - 360f; } else if (lNewLocalEuler.x < -180f) { lNewLocalEuler.x = lNewLocalEuler.x + 360f; } float lYaw = (_MinYaw > -180f || _MaxYaw < 180f ? Mathf.Clamp(lNewLocalEuler.y, _MinYaw, _MaxYaw) : lNewLocalEuler.y); float lPitch = Mathf.Clamp(lNewLocalEuler.x, _MinPitch, _MaxPitch); if (lYaw != lNewLocalEuler.y || lPitch != lNewLocalEuler.x) { lNewCameraRotation = (RigController.RotateAnchorOffset ? Anchor.transform.rotation : Quaternion.identity) * Quaternion.Euler(lPitch, lYaw, 0f); lNewCameraPosition = lNewFocusPosition - (lNewCameraRotation.Forward() * mDistance); } // Return the results mRigTransform.Position = lNewCameraPosition; mRigTransform.Rotation = lNewCameraRotation; return(mRigTransform); }
/// <summary> /// Grabs the euler changes that happen this frame. /// </summary> /// <param name="rAnchorYaw">Anchor yaw that should be added</param> /// <returns></returns> public virtual Vector3 GetFrameEuler(bool rUseYawLimits, bool rUsePitchLimits = true) { Vector3 lFrameEuler = Vector3.zero; // If the camera was moved externally, we need to recalculate the last focus position if (RigController.LastPosition != RigController._Transform.position) { mFocusLastPosition = GetFocusPosition(RigController._Transform.rotation); } // Now we can grab the movement if (mTarget != null) { mTargetForward = (mTarget.position - mRigTransform.Position).normalized; } if (mTargetForward.sqrMagnitude > 0f) { Quaternion lTargetRotation = Quaternion.LookRotation(mTargetForward, Anchor.up); Quaternion lLocalRotation = Quaternion.Inverse(Anchor.rotation) * RigController._Transform.rotation; Quaternion lLocalTargetRotation = Quaternion.Inverse(Anchor.rotation) * lTargetRotation; float lDeltaYaw = lLocalTargetRotation.eulerAngles.y - lLocalRotation.eulerAngles.y; if (lDeltaYaw > 180f) { lDeltaYaw = lDeltaYaw - 360f; } else if (lDeltaYaw < -180f) { lDeltaYaw = lDeltaYaw + 360f; } float lDeltaPitch = lLocalTargetRotation.eulerAngles.x - lLocalRotation.eulerAngles.x; if (lDeltaPitch > 180f) { lDeltaPitch = lDeltaPitch - 360f; } else if (lDeltaPitch < -180f) { lDeltaPitch = lDeltaPitch + 360f; } if (mTargetYawSpeed <= 0f) { lFrameEuler.y = lDeltaYaw; } else { lFrameEuler.y = Mathf.Sign(lDeltaYaw) * Mathf.Min(mTargetYawSpeed * Time.deltaTime, Mathf.Abs(lDeltaYaw)); } if (mTargetPitchSpeed <= 0f) { lFrameEuler.x = lDeltaPitch; } else { lFrameEuler.x = Mathf.Sign(lDeltaPitch) * Mathf.Min(mTargetPitchSpeed * Time.deltaTime, Mathf.Abs(lDeltaPitch)); } if (Mathf.Abs(lFrameEuler.y) < EPSILON && Mathf.Abs(lFrameEuler.x) < EPSILON) { _Euler.y = LocalYaw; _Euler.x = LocalPitch; _EulerTarget = _Euler; if (mAutoClearTarget) { mTargetForward = Vector3.zero; } } } else if (mTargetYaw < float.MaxValue || mTargetPitch < float.MaxValue) { if (mTargetYaw < float.MaxValue) { float lDeltaYaw = mTargetYaw - LocalYaw; if (mTargetYawSpeed <= 0f) { lFrameEuler.y = lDeltaYaw; } else { lFrameEuler.y = Mathf.Sign(lDeltaYaw) * Mathf.Min(mTargetYawSpeed * Time.deltaTime, Mathf.Abs(lDeltaYaw)); } // TRT 04/09/2017 - Added to stop trying to reach the target when the character is rotating Transform lAnchorTransform = Anchor; float lAnchorRootYawDelta = Vector3Ext.HorizontalAngleTo(mAnchorLastRotation.Forward(), lAnchorTransform.forward, lAnchorTransform.up); if (Mathf.Abs(lFrameEuler.y) - Mathf.Abs(lAnchorRootYawDelta * 2f) < EPSILON) { _Euler.y = mTargetYaw; _EulerTarget.y = mTargetYaw; //if (mAutoClearTarget) { mTargetYaw = float.MaxValue; } } if (mAutoClearTarget && Mathf.Abs(lDeltaYaw) < EPSILON) { mTargetYaw = float.MaxValue; } } if (mTargetPitch < float.MaxValue) { float lDeltaPitch = mTargetPitch - LocalPitch; if (mTargetPitchSpeed <= 0f) { lFrameEuler.x = lDeltaPitch; } else { lFrameEuler.x = Mathf.Sign(lDeltaPitch) * Mathf.Min(mTargetPitchSpeed * Time.deltaTime, Mathf.Abs(lDeltaPitch)); } if (Mathf.Abs(lFrameEuler.x) < EPSILON) { _Euler.x = mTargetPitch; _EulerTarget.x = mTargetPitch; //if (mAutoClearTarget) { mTargetPitch = float.MaxValue; } } if (mAutoClearTarget && Mathf.Abs(lDeltaPitch) < EPSILON) { mTargetPitch = float.MaxValue; } } } else { IInputSource lInputSource = RigController.InputSource; if (lInputSource.IsViewingActivated) { if (_IsYawEnabled && lFrameEuler.y == 0f) { lFrameEuler.y = lInputSource.ViewX * mDegreesYPer60FPSTick; } if (_IsPitchEnabled && lFrameEuler.x == 0f) { lFrameEuler.x = (RigController._InvertPitch || _InvertPitch ? -1f : 1f) * lInputSource.ViewY * mDegreesXPer60FPSTick; } } // Grab the smoothed yaw _EulerTarget.y = (rUseYawLimits && (_MinYaw > -180f || _MaxYaw < 180f) ? Mathf.Clamp(_EulerTarget.y + lFrameEuler.y, _MinYaw, _MaxYaw) : _EulerTarget.y + lFrameEuler.y); lFrameEuler.y = (_Smoothing <= 0f ? _EulerTarget.y : SmoothDamp(_Euler.y, _EulerTarget.y, _Smoothing * 0.001f, Time.deltaTime)) - _Euler.y; _Euler.y = _Euler.y + lFrameEuler.y; // Grab the smoothed pitch _EulerTarget.x = (rUsePitchLimits && (_MinPitch > -180f || _MaxPitch < 180f) ? Mathf.Clamp(_EulerTarget.x + lFrameEuler.x, _MinPitch, _MaxPitch) : _EulerTarget.x + lFrameEuler.x); lFrameEuler.x = (_Smoothing <= 0f ? _EulerTarget.x : SmoothDamp(_Euler.x, _EulerTarget.x, _Smoothing * 0.001f, Time.deltaTime)) - _Euler.x; _Euler.x = _Euler.x + lFrameEuler.x; } return(lFrameEuler); }
/// <summary> /// Grabs the combatants that fit the specified criteria /// </summary> /// <param name="rHunter">Transform who is searching for the combatants</param> /// <param name="rSeekOrigin">Combat origin of the hunter</param> /// <param name="rFilter">Filters we'll use to limit which combatants are returned</param> /// <param name="rCombatantHits">List of CombatantHit values who are the combatants</param> /// <param name="rIgnore">Transform that we won't consider a target (typically the character)</param> /// <returns>Count of combatants returned</returns> public static int QueryCombatTargets(Transform rSeeker, Vector3 rSeekOrigin, CombatFilter rFilter, List <CombatTarget> rCombatTargets, Transform rIgnore) { if (rSeeker == null) { return(0); } if (rCombatTargets == null) { return(0); } #if OOTII_PROFILE com.ootii.Utilities.Profiler.Start(rSeeker.name + ".QueryCombatTargets"); #endif Collider[] lHitColliders; rCombatTargets.Clear(); int lHitCount = RaycastExt.SafeOverlapSphere(rSeekOrigin, rFilter.MaxDistance, out lHitColliders, rFilter.Layers, rIgnore); for (int i = 0; i < lHitCount; i++) { GameObject lGameObject = lHitColliders[i].gameObject; // Don't count the ignore if (lGameObject.transform == rSeeker) { continue; } if (lGameObject.transform == rIgnore) { continue; } // Determine if the combatant has the appropriate tag if (rFilter.Tag != null && rFilter.Tag.Length > 0) { if (!lGameObject.CompareTag(rFilter.Tag)) { continue; } } // We only care about combatants we'll enage with ICombatant lCombatant = null; Transform lHitTransform = lHitColliders[i].transform; while (lHitTransform != null) { lCombatant = lGameObject.GetComponent <ICombatant>(); if (lCombatant != null) { break; } lHitTransform = lHitTransform.parent; } if (rFilter.RequireCombatant && lCombatant == null) { continue; } // Determine if the combatant is within range Vector3 lClosestPoint = Vector3.zero; ActorController lActorController = lGameObject.GetComponent <ActorController>(); if (lActorController != null) { lClosestPoint = lActorController.ClosestPoint(rSeekOrigin); } else { lClosestPoint = GeometryExt.ClosestPoint(rSeekOrigin, lHitColliders[i]); } // If we have an invalid point, stop if (lClosestPoint == Vector3Ext.Null) { continue; } // Determine if the point is in range bool lIsValid = true; Vector3 lToClosestPoint = lClosestPoint - rSeekOrigin; float lDistance = lToClosestPoint.magnitude; if (rFilter.MinDistance > 0f && lDistance < rFilter.MinDistance) { lIsValid = false; } if (rFilter.MaxDistance > 0f && lDistance > rFilter.MaxDistance) { lIsValid = false; } // Ensure we're not ontop of the combatant. In that case, it's probably the ground Vector3 lDirection = lToClosestPoint.normalized; if (lDirection == -rSeeker.up) { lIsValid = false; } // Check if we're within the field of view float lHAngle = Vector3Ext.HorizontalAngleTo(rSeeker.forward, lDirection, rSeeker.up); if (rFilter.HorizontalFOA > 0f && Mathf.Abs(lHAngle) > rFilter.HorizontalFOA * 0.5f) { lIsValid = false; } float lVAngle = Vector3Ext.HorizontalAngleTo(rSeeker.forward, lDirection, rSeeker.right); if (rFilter.VerticalFOA > 0f && Mathf.Abs(lVAngle) > rFilter.VerticalFOA * 0.5f) { lIsValid = false; } // This is an odd test, but we have to do it. If the closest point of a sphere is out of the FOA, it may // be that the top of the sphere is in the FOA. So, we'll grab the top of the sphere and test it. if (!lIsValid && lCombatant != null && lHitColliders[i] is SphereCollider) { lIsValid = true; SphereCollider lSphereCollider = lHitColliders[i] as SphereCollider; lClosestPoint = lCombatant.Transform.position + (lCombatant.Transform.rotation * (lSphereCollider.center + (Vector3.up * lSphereCollider.radius))); lToClosestPoint = lClosestPoint - rSeekOrigin; lDistance = lToClosestPoint.magnitude; if (rFilter.MinDistance > 0f && lDistance < rFilter.MinDistance) { lIsValid = false; } if (rFilter.MaxDistance > 0f && lDistance > rFilter.MaxDistance) { lIsValid = false; } // Ensure we're not ontop of the combatant. In that case, it's probably the ground lDirection = lToClosestPoint.normalized; if (lDirection == -rSeeker.up) { lIsValid = false; } // Check if we're within the field of view lHAngle = Vector3Ext.HorizontalAngleTo(rSeeker.forward, lDirection, rSeeker.up); if (rFilter.HorizontalFOA > 0f && Mathf.Abs(lHAngle) > rFilter.HorizontalFOA * 0.5f) { lIsValid = false; } lVAngle = Vector3Ext.HorizontalAngleTo(rSeeker.forward, lDirection, rSeeker.right); if (rFilter.VerticalFOA > 0f && Mathf.Abs(lVAngle) > rFilter.VerticalFOA * 0.5f) { lIsValid = false; } } if (mShowDebug) { GraphicsManager.DrawPoint(rSeekOrigin, Color.white, null, 2f); GraphicsManager.DrawPoint(lClosestPoint, Color.red, null, 2f); } if (lIsValid) { // Add the combatant to our list CombatTarget lTargetInfo = new CombatTarget(); lTargetInfo.SeekOrigin = rSeekOrigin; lTargetInfo.Collider = lHitColliders[i]; lTargetInfo.Combatant = lCombatant; lTargetInfo.ClosestPoint = lClosestPoint; lTargetInfo.Distance = lDistance; lTargetInfo.Direction = lDirection; lTargetInfo.HorizontalAngle = lHAngle; lTargetInfo.VerticalAngle = lVAngle; rCombatTargets.Add(lTargetInfo); } } // Sort the combatants by distance if (rCombatTargets.Count > 1) { rCombatTargets.Sort((rLeft, rRight) => rLeft.Distance.CompareTo(rRight.Distance)); // We also want to remove duplicates for (int i = 0; i < rCombatTargets.Count; i++) { Transform lTarget = rCombatTargets[i].Collider.transform; if (rCombatTargets[i].Combatant != null) { lTarget = rCombatTargets[i].Combatant.Transform; } // Check if there's a duplicate and remove it for (int j = rCombatTargets.Count - 1; j > i && j > 0; j--) { Transform lNextTarget = rCombatTargets[j].Collider.transform; if (rCombatTargets[j].Combatant != null) { lNextTarget = rCombatTargets[j].Combatant.Transform; } if (lNextTarget == lTarget) { rCombatTargets.RemoveAt(j); } } } } #if OOTII_PROFILE float lTime = Utilities.Profiler.Stop(rSeeker.name + ".QueryCombatTargets"); //Utilities.Debug.Log.FileWrite(rSeeker.name + ".QueryCombatTargets time:" + lTime.ToString("f5") + "ms"); #endif // Finally, return the count return(rCombatTargets.Count); }