public override void Start() { if (CheckErrors() == true) { return; } base.Start(); terminalVel = MFcompute.FindTerminalVelocity(totalThrust, myRigidbody); OnValidate(); }
public override void Awake() { if (CheckErrors() == true) { return; } base.Awake(); turnSpeed = turnRate; // used by FS_Targeting to evaluate time to aim terminalVel = MFcompute.FindTerminalVelocity(totalThrust, myRigidbody); OnValidate(); }
public static float SmoothRateChange(float distance, float closureRate, float curRate, float rateAccel, float decelMult, float rateMax) { int _goalFactor = distance >= 0 ? 1 : -1; // which way is goal? int _curTurnFactor = curRate >= 0 ? 1 : -1; // which way currently turning? // if goal is within accelleration, reduce accel to match goal. This minimizes jitter at high acceleration speeds if ((distance <= rateAccel * _curTurnFactor * Time.deltaTime && distance >= 0) || (distance >= rateAccel * _curTurnFactor * Time.deltaTime && distance <= 0)) { rateAccel = Mathf.Abs(distance) / Time.deltaTime; } float _rateDecel = rateAccel * decelMult * 2f; // decelerate faster than accelerating (*2 to make it a little more agressive as default) // determine which way to accelerate if (_goalFactor * _curTurnFactor > 0) { // positive: moving towards goal if (distance * _goalFactor >= MFcompute.DistanceToStop(closureRate, _rateDecel) * _goalFactor) { // turn faster curRate = Mathf.Clamp(curRate + (rateAccel * _curTurnFactor * Time.deltaTime), -rateMax, rateMax); } else { if (distance * _goalFactor <= MFcompute.DistanceToStop(closureRate, _rateDecel) * _goalFactor * .8) { //turn slower curRate = Mathf.Clamp(curRate - (_rateDecel * _curTurnFactor * Time.deltaTime), -rateMax, rateMax); } // else don't change } } else { // negative: moving away from goal, start heading towards goal curRate = Mathf.Clamp(curRate + (_rateDecel * _goalFactor * Time.deltaTime), -rateMax, rateMax); } return(curRate); }
public virtual void FixedUpdate() { velocity = UnitVelocity(); if (_navTarget) { navTargetAim = _navTarget.position; if (useIntercept == true) { if (interceptForPlatform && interceptForPlatform.target == _navTarget) { // intercept for this target already computed by platform navTargetAim = interceptForPlatform.targetAimLocation - (interceptForPlatform.weaponMount.position - transform.position); } else { float _vel = velocity.magnitude; // use my speed if (interceptForPlatform) { _vel = interceptForPlatform.shotSpeed ?? 0f; } // use platform shot speed // _navTarget velocity Vector3 _navVelocity = Vector3.zero; if (navRigidbody) { _navVelocity = navRigidbody.velocity; } else { _navVelocity = navLastPosition - _navTarget.position; navLastPosition = _navTarget.position; } navTargetAim = MFcompute.Intercept(transform.position, velocity, _vel, _navTarget.position, _navVelocity) ?? _navTarget.position; } } } }
public virtual Vector3 AimLocation() { // adjust aim location to hit target Vector3 _aimLoc = Vector3.zero; if (_target) { _aimLoc = _target.position; if (useIntercept == true && shotSpeed != null) { Vector3 _targetVelocity = Vector3.zero; // target velocity if (targetRigidbody) // if target has a rigidbody, use velocity { _targetVelocity = targetRigidbody.velocity; } else // otherwise compute velocity from change in position { _targetVelocity = (_aimLoc - lastTargetPosition) / Time.deltaTime; lastTargetPosition = _aimLoc; } // point at linear intercept position Vector3?interceptAim = MFcompute.Intercept(exitLoc, velocity, (float)shotSpeed, _aimLoc, _targetVelocity); if (interceptAim == null) { target = null; } else { _aimLoc = (Vector3)interceptAim; } } } return(_aimLoc); }
public static float?BallisticIteration(Vector3 exitLoc, float?shotSpeed, float aimRad, MFnum.ArcType arc, Transform target, Vector3 targetVelocity, Vector3 platformVelocity, Vector3 aimLoc_In, out Vector3 aimLoc_Out) { float?_ballAim = null; aimLoc_Out = aimLoc_In; // find new flight time float?_flightTime = MFball.BallisticFlightTime(aimLoc_In, exitLoc, (float)shotSpeed, aimRad, arc); float _effectiveShotSpeed = Vector3.Distance(exitLoc, aimLoc_In) / (float)_flightTime; // find intercept based on new _effectiveShotSpeed Vector3?_intAim = MFcompute.Intercept(exitLoc, platformVelocity, _effectiveShotSpeed, target.position, targetVelocity); if (_intAim == null) { _ballAim = null; } else { aimLoc_Out = (Vector3)_intAim; // modify target aim location // re-calculate ballistic trajectory based on intercept point _ballAim = MFball.BallisticAimAngle(aimLoc_Out, exitLoc, (float)shotSpeed, arc); } return(_ballAim); }
public override Vector3 AimLocation() { // intercept and ballistics if ( _target ) { Vector3 _targetVelocity = Vector3.zero; targetLoc = _target.position; if ( useIntercept == true && shotSpeed != null ) { // target velocity if (targetRigidbody) { // if target has a rigidbody, use velocity _targetVelocity = targetRigidbody.velocity; } else { // otherwise compute velocity from change in position _targetVelocity = ( targetLoc - lastTargetPosition ) / Time.deltaTime; lastTargetPosition = targetLoc; } } if ( useGravity == true && Physics.gravity.y != 0 && shotSpeed != null ) { // ballistic aim int _factor = -Physics.gravity.y > 0 ? 1 : -1; // find initial aim angle float? _ballAim = MFball.BallisticAimAngle( targetLoc, exitLoc, (float)shotSpeed, curArc ); if ( _ballAim == null ) { target = null; } else { if ( useIntercept == true && playerControl == false ) { // ballistic + intercept // iterate for better ballistic accuracy when also using intercept int bi = 0; int biMax; if ( curArc == MFnum.ArcType.High ) { biMax = highArcBallisticIterations; } else { biMax = ballisticIterations; } while ( _target && bi++ < biMax ) { // _ballAim = BallisticIteration ( exitLoc, (float)shotSpeed, (float)_ballAim, _targetVelocity ); _ballAim = MFball.BallisticIteration ( exitLoc, (float)shotSpeed, (float)_ballAim, curArc, _target, _targetVelocity, velocity, targetLoc, out targetLoc ); if ( _ballAim == null ) { target = null; } // no solution, release target } } if ( _target ) { // target can become null in balistic iteration if no solution Vector3 _cross = -Vector3.Cross( ( targetLoc - exitLoc ), Vector3.up ); Quaternion _eleAngleDir = Quaternion.AngleAxis( _factor * (float)_ballAim * Mathf.Rad2Deg, -_cross ); targetAimLocation = exitLoc + (( _eleAngleDir * Vector3.Cross( _cross, Vector3.up ) ).normalized * targetRange ); } } } else { // no gravity if ( useIntercept == true && playerControl == false && shotSpeed != null ) { // point at linear intercept position Vector3? _interceptAim = MFcompute.Intercept( exitLoc, velocity, (float)shotSpeed, targetLoc, _targetVelocity ); if ( _interceptAim == null ) { target = null; } else { targetAimLocation = (Vector3)_interceptAim; } } else { // point at target position targetAimLocation = _target.position; } } } else { targetAimLocation = Vector3.zero; } return targetAimLocation; }
public override void Update () { if (error == true) { return; } base.Update(); // AimLocation() called from base // find rotation/elevation rates float _useTime; if (Time.time < .5) { // _useTime = Time.deltaTime; // to avoid smoothDeltaTime returning NaN on the first few frames return; // avoids initial movement spurt } else { _useTime = Time.smoothDeltaTime; } float _curRotRateEst = Quaternion.Angle( rotator.localRotation, lastRotation ) / _useTime; float _curEleRateEst = Quaternion.Angle( elevator.localRotation, lastElevation ) / _useTime; // average rotation, then move to lastRotation averageRotRateEst = (_curRotRateEst + lastRotRateEst) / 2f; lastRotRateEst = _curRotRateEst; lastRotation = rotator.localRotation; // average elevation, then move to lastElevation averageEleRateEst = (_curEleRateEst + lastEleRateEst) / 2f; lastEleRateEst = _curEleRateEst; lastElevation = elevator.localRotation; // find inaccuracy caused by rotation/elevation. (using .03 to minimize influence from spikes) if ( averageRotRateEst > rotationAccel * .03f || averageEleRateEst > elevationAccel * .03f ) { totalTurnWeapInaccuracy = turningWeapInaccuracy * (averageRotRateEst + averageEleRateEst); // totalTurnAimInaccuracy = turningAimInaccuracy * (averageRotRateEst + averageEleRateEst); } else { totalTurnWeapInaccuracy = 0f; // totalTurnAimInaccuracy = 0f; } // move aim error location // if ( aimError + totalTurnAimInaccuracy > 0 ) { if ( aimError > 0 ) { if ( Time.time >= lastAimError + aimErrorInterval ) { systAimError = Random.insideUnitSphere; lastAimError = Time.time; } } // find aim locations if ( _target ) { Vector3 _localTarget; timeSinceTarget = Time.time; // move apparent location of target due to turret aim error // if ( aimError + totalTurnAimInaccuracy > 0 ) { if ( aimError > 0 ) { // targetAimLocation = targetAimLocation + ((systAimError * (aimError + totalTurnAimInaccuracy) * targetRange) / 57.3f ); // 57.3 is the distance where 1 unit offset appears as 1° offset targetAimLocation = targetAimLocation + ((systAimError * aimError * targetRange) / 57.3f ); // 57.3 is the distance where 1 unit offset appears as 1° offset } // find target's location in rotation plane _localTarget = rotator.InverseTransformPoint( targetAimLocation ); rotatorPlaneTarget = rotator.TransformPoint( new Vector3(_localTarget.x, 0f, _localTarget.z) ); // find target's location in elevation plane as if rotator is already facing target, as rotation will eventualy bring it to front. (don't elevate past 90/-90 degrees to reach target) Vector3 _cross = Vector3.Cross( rotator.up, targetAimLocation - weaponMount.position ); Vector3 _level = Vector3.Cross( _cross, rotator.up ); // find direction towards target but level with local plane float _angle = Vector3.Angle( _level, targetAimLocation - weaponMount.position ); if ( _localTarget.y < elevator.localPosition.y + weaponMount.localPosition.y ) { _angle *= -1; } // should angle be negative? elevatorPlaneTarget = weaponMount.position + (Quaternion.AngleAxis( _angle, -rotator.right ) * rotator.forward * 1000f); // Debug.DrawRay( weaponMount.position, _cross, Color.red, .01f ); // Debug.DrawRay( weaponMount.position, _level, Color.green, .01f ); // Debug.DrawRay( weaponMount.position, targetAimLocation - weaponMount.position, Color.cyan, .01f ); // Debug.Log( _angle ); } else { // no target targetAimLocation = Vector3.zero; if ( Time.time >= timeSinceTarget + restDelay ) { // set rotation and elevation goals to the rest position rotatorPlaneTarget = rotator.position + (Quaternion.AngleAxis(restAngle.y, transform.up) * transform.forward * 1000f); elevatorPlaneTarget = elevator.position + (Quaternion.AngleAxis(restAngle.x, -rotator.right) * rotator.forward * 1000f); } } // turning _curRotSeperation = MFmath.AngleSigned(rotator.forward, rotatorPlaneTarget - weaponMount.position, transform.up); _curEleSeperation = MFmath.AngleSigned(elevator.forward, elevatorPlaneTarget - weaponMount.position, -rotator.right); // turn opposite if shortest route is through a gimbal limit float _bearing = MFmath.AngleSigned(transform.forward, rotatorPlaneTarget - weaponMount.position, transform.up); float _aimAngle = MFmath.AngleSigned(transform.forward, rotator.forward, rotator.up); float _modAccel = rotationAccel; float _modSeperation = _curRotSeperation; if (limitLeft > -180 || limitRight < 180) { // is there a gimbal limit? if ( (_curRotSeperation < 0 && _bearing > 0 && _aimAngle < 0) || (_curRotSeperation > 0 && _bearing < 0 && _aimAngle > 0) ) { // is shortest turn angle through a limit? // reverse turn _modAccel *= -1f; // for constant turn rate mode _modSeperation *= -1f; // for smooth turn rate mode } } Quaternion _rot; if (constantTurnRate == true) { // no variation in rotation speed. more accurate, lightweight, less realistic. // apply rotation _rot = Quaternion.LookRotation( rotatorPlaneTarget - rotator.position, transform.up ); rotator.rotation = Quaternion.RotateTowards( rotator.rotation, _rot, Mathf.Min(_modAccel, rotationRateMax) * Time.deltaTime ); // apply elevation _rot = Quaternion.LookRotation( elevatorPlaneTarget - weaponMount.position, rotator.up ); elevator.rotation = Quaternion.RotateTowards( elevator.rotation, _rot, Mathf.Min(elevationAccel, elevationRateMax) * Time.deltaTime ); } else { // accellerate/decellerate to rotation goal // find closure rate of angle to target. float _closureTurnRate = (_curRotSeperation - lastRotSeperation) / Time.deltaTime; float _closureEleRate = (_curEleSeperation - lastEleSeperation) / Time.deltaTime; // store current rate and seperation to become last rate/seperation for next time lastRotSeperation = _curRotSeperation; lastEleSeperation = _curEleSeperation; // apply rotation curRotRate = MFcompute.SmoothRateChange(_modSeperation, _closureTurnRate, curRotRate, rotationAccel, decelerateMult, rotationRateMax); rotator.rotation = Quaternion.AngleAxis(curRotRate * Time.deltaTime, transform.up) * rotator.rotation; // apply elevation curEleRate = MFcompute.SmoothRateChange(_curEleSeperation, _closureEleRate, curEleRate, elevationAccel, decelerateMult, elevationRateMax); elevator.rotation = Quaternion.AngleAxis(curEleRate * Time.deltaTime, -rotator.right) * elevator.rotation; } CheckGimbalLimits(); // prevent nonsence rotator.localEulerAngles = new Vector3( 0f, rotator.localEulerAngles.y, 0f ); elevator.localEulerAngles = new Vector3( elevator.localEulerAngles.x, 0f, 0f ); // turn sounds if (rotatorSound.audioSource) { TurnSound( rotatorSound, averageRotRateEst, rotationRateMax ); } if (elevatorSound.audioSource) { TurnSound( elevatorSound, averageEleRateEst, elevationRateMax ); } }