using System.Collections.Generic; using UnityEngine; // Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace SciFiShipController { /// /// Class for a moving platform. /// Similar to the StickyMovingPlatform found in Sticky3D Controller (a character controller by SCSM) /// [AddComponentMenu("Sci-Fi Ship Controller/Utilities/Moving Platform")] [HelpURL("http://scsmmedia.com/ssc-documentation")] public class SSCMovingPlatform : MonoBehaviour { #region Enumerations public enum MoveUpdateType { Update = 0, FixedUpdate = 1 } #endregion #region Public Variables /// /// If enabled, Initialise() will be called as soon as Awake() runs. This should be disabled if you want to control /// when the SSC Moving Platform is enabled through code. /// public bool initialiseOnAwake = false; /// /// The update loop or timing to use for moving and/or rotating the platform. /// At runtime call SetMoveUpdateType() to change it. /// public MoveUpdateType moveUpdateType = MoveUpdateType.Update; /// /// Whether the platform moves. /// public bool move = true; /// /// Use positions relative to the initial gameobject position, rather than /// absolute world space positions. /// public bool useRelativePositions = false; /// /// List of positions the platform will move to (in order). Call Initialise() if you modify this. /// public List positions = new List(new Vector3[] { Vector3.zero, Vector3.forward * 5f }); /// /// Average movement speed of the platform in metres per second. /// To update this while the platform is moving, call UpdateAverageMoveSpeed(..). /// public float averageMoveSpeed = 5f; /// /// The time the platform waits at the first position. /// public float startWaitTime = 0f; /// /// The time the platform waits at the last position /// public float endWaitTime = 0f; /// /// The "profile" of the platform's movement. Use this to make the movement more or less smooth. /// Call RefreshInverseCurve() after changing this at runtime. /// public AnimationCurve movementProfile = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); /// /// The maximum time it takes the platform to come to a stop when smoothStop is used with StopPlatform() /// [Range(0f, 10f)] public float smoothStopTime = 2f; /// /// The maximum time it takes the platform to come to resume normal speed when smoothStart is used with StartPlatform() /// [Range(0f, 10f)] public float smoothStartTime = 2f; /// /// Whether the platform rotates. /// public bool rotate = false; /// /// The starting rotation of the platform. /// public Vector3 startingRotation = Vector3.zero; /// /// The axis of rotation of the platform. /// public Vector3 rotationAxis = Vector3.up; /// /// The rotational speed of the platform in degrees per second. /// public float rotationSpeed = 180f; /// /// The audio source containing the clip to play for the platform. /// Must be a child of the platform gameobject. /// Call ResetAudioSettings() if changed at runtime. /// public AudioSource platformAudio = null; /// /// Overall volume of sound for the platform /// [Range(0f, 1f)] public float overallAudioVolume = 0.5f; /// /// The sound that is played while the platform is moving /// public AudioClip inTransitAudioClip = null; /// /// The sound that is played when the platform arrives at the first position. /// This does not play when it is first initialised. /// public AudioClip audioArrivedStartClip = null; /// /// The relative volume the arrived start audio clip is played /// [Range(0f, 1f)] public float audioArrivedStartVolume = 1f; /// /// The sound that is played when the platform arrives at the last position /// public AudioClip audioArrivedEndClip = null; /// /// The relative volume the arrived end audio clip is played /// [Range(0f, 1f)] public float audioArrivedEndVolume = 1f; /// /// These are triggered by a moving platform when it arrives at the start position /// public SSCMovingPlatformEvt1 onArriveStart = null; /// /// These are triggered by a moving platform when it arrives at the end position /// public SSCMovingPlatformEvt1 onArriveEnd = null; /// /// These are triggered by a moving platform when it departs from the start position /// public SSCMovingPlatformEvt1 onDepartStart = null; /// /// These are triggered by a moving platform when it departs from the end position /// public SSCMovingPlatformEvt1 onDepartEnd = null; #endregion Public Variables #region Public Properties /// /// The unique identifier of this platform during this session /// public int PlatformId { get { return platformId; } } /// /// If Move is enabled, is the platform waiting at the starting position? /// public bool IsWaitingAtStartPosition { get { return isInitialised && move && isWaitingAtStartPosition; } } /// /// If Move is enabled, is the platform waiting at the end or last position? /// public bool IsWaitingAtEndPosition { get { return isInitialised && move && isWaitingAtEndPosition; } } #endregion #region Private Variables /// /// Is the platform ready for use? /// private bool isInitialised = false; private int platformId = -1; private bool isMoveFixedUpdate = false; /// /// World space origin for relative positions. /// private Vector3 originPosition = Vector3.zero; /// /// The rotation of the platform at the origin, used to determine /// relative offset positions when useRelativePositions is true. /// private Quaternion originRotation = Quaternion.identity; /// /// The number of positions in the array. /// private int numPositions = 0; /// /// The index of the position the platform was last at /// private int lastPositionIndex = 0; /// /// The index of the position the platform should move towards. /// private int nextPositionIndex = 0; /// /// The last worldspace position the platform visited. /// private Vector3 lastPosition = Vector3.zero; /// /// The worldspace position the platform is now moving towards. /// private Vector3 nextPosition = Vector3.zero; /// /// The total time it will take to travel from the last position to the next position. /// private float travelTimeToNextPosition = 0f; /// /// The total distance from the last position to the next position. /// private float travelDistToNextPosition = 0f; /// /// The time elapsed since the platform left the last position. /// private float travelTimer = 0f; /// /// The time elaspsed since the platform reached the last position. /// private float waitTimer = 0f; /// /// Used with smooth start/stop /// private float smoothTimer = 0f; /// /// Is the platform stopped between start and end positions? /// private bool isStoppedInTransit = false; /// /// Only applies if manually stopped with StopPlatform(..) /// private bool isSmoothStopEnabled = false; /// /// Only applies if manually started with StartPlatform(..). /// private bool isSmoothStartEnabled = false; /// /// The current rate or acceleration (or decceleration) /// when smoothly stopping or starting /// private float smoothAcceleration = 0f; private float smoothInitialSpeed = 0f; private Vector3 smoothInitialPosition = Vector3.zero; private float smoothDistanceToTravel = 0f; /// /// Whether we are currently waiting at the first position. /// private bool isWaitingAtStartPosition = false; /// /// Whether we are currently waiting at the last position. /// private bool isWaitingAtEndPosition = false; /// /// Can audio clips be played? /// private bool isAudioAvailable = false; /// /// The optional rigidbody attached to the platform /// private Rigidbody rBody = null; /// /// Is there a rigidbody attached to the platform? /// private bool isRigidBody = false; /// /// Is the plaftorm currently attempting to reverse its /// direction of travel? /// private bool isReversingDirection = false; /// /// The current world space position of the platform /// private Vector3 currentWorldPosition = Vector3.zero; /// /// The world space position of the platform in the last frame /// private Vector3 previousWorldPosition = Vector3.zero; /// /// The current world space rotation of the platform /// private Quaternion currentWorldRotation = Quaternion.identity; /// /// Inverts time and distance so we can find the current time /// from a distance between points. /// private AnimationCurve invMovementProfile = null; #endregion Private Variables #region Awake and Update Methods // Awake is called before the first frame update void Awake() { // Initialise the platform if (initialiseOnAwake) { Initialise(); } } private void Update() { if (!isMoveFixedUpdate && isInitialised && !isStoppedInTransit) { UpdatePlatform(); } } private void FixedUpdate() { if (isMoveFixedUpdate && isInitialised && !isStoppedInTransit) { UpdatePlatform(); } } #endregion Awake and Update Methods #region Private Member Methods private void UpdatePlatform() { // Movement if (move && numPositions > 1 && Time.deltaTime > 0f) { previousWorldPosition = currentWorldPosition; if (isSmoothStopEnabled || isSmoothStartEnabled) { // Increment the smooth movement timer smoothTimer += Time.deltaTime; // Calculate the distance we need to be displaced from the initial position float dist = smoothInitialSpeed * smoothTimer + (0.5f * smoothAcceleration * (smoothTimer * smoothTimer)); // Move towards the next position Vector3 direction = (nextPosition - lastPosition).normalized; currentWorldPosition = smoothInitialPosition + direction * dist; // Smoothly move the platform MovePlatform(); if (isSmoothStartEnabled) { // Smooth start // Calculate the current curve time float currentCurveTime = CalculateCurrentCurveTime(); // Calculate the current curve speed float currentCurveSpeed = CalculateCurveSpeed(currentCurveTime); // Calculate our current speed float currentPlatformSpeed = smoothAcceleration * smoothTimer; //Debug.Log("Platform: " + currentPlatformSpeed.ToString("0.000") + " m/s, curve: " + currentCurveSpeed.ToString("0.000") + " m/s"); // If our current speed is greater than or equal to the current curve speed, switch to normal movement if (currentPlatformSpeed >= currentCurveSpeed) { isSmoothStartEnabled = false; travelTimer = currentCurveTime; } } else { // Smooth stop if (smoothTimer > smoothStopTime || dist >= smoothDistanceToTravel) { move = false; isSmoothStopEnabled = false; // Switch the direction platform is moving if required if (isReversingDirection) { ChangeDirection(); isReversingDirection = false; // If smooth stop, assume a smooth start. StartPlatform(true); } } } } else if (isWaitingAtStartPosition) { // Increment the waiting timer waitTimer += Time.deltaTime; // Check if the timer has exceeded the waiting time if (waitTimer > startWaitTime) { // Stop waiting isWaitingAtStartPosition = false; PlayInTransitAudio(); if (onDepartStart != null) { onDepartStart.Invoke(true, false, platformId, Vector3.zero); } } } else if (isWaitingAtEndPosition) { // Increment the waiting timer waitTimer += Time.deltaTime; // Check if the timer has exceeded the waiting time if (waitTimer > endWaitTime) { // Stop waiting isWaitingAtEndPosition = false; PlayInTransitAudio(); if (onDepartEnd != null) { onDepartEnd.Invoke(false, true, platformId, Vector3.zero); } } } else { // Increment the movement timer travelTimer += Time.deltaTime; // Check if the timer has exceeded the travel time if (travelTimer > travelTimeToNextPosition) { // Start moving towards the next position GoToNextPosition(); } else { // Move the platform to the correct position using the movement profile float movementProfileValue = movementProfile.Evaluate(travelTimer / travelTimeToNextPosition); currentWorldPosition = lastPosition + (nextPosition - lastPosition) * movementProfileValue; MovePlatform(); if (isAudioAvailable && platformAudio.isPlaying && platformAudio.isActiveAndEnabled) { platformAudio.volume = overallAudioVolume * movementProfileValue; } } } } // Rotation if (rotate) { // If we have a rigidbody and this is being called from FixedUpdate(), rotate the rigidbody rather than the transform. if (isRigidBody && isMoveFixedUpdate) { currentWorldRotation = Quaternion.Euler(rotationAxis.normalized * rotationSpeed * Time.deltaTime) * rBody.rotation; rBody.MoveRotation(currentWorldRotation); } else { currentWorldRotation = Quaternion.Euler(rotationAxis.normalized * rotationSpeed * Time.deltaTime) * transform.rotation; transform.rotation = currentWorldRotation; } } } /// /// Change the last/next positions around (and do nothing else) /// private void ChangeDirection() { int oldNextPosIndex = nextPositionIndex; int oldLastPosIndex = lastPositionIndex; Vector3 oldNextPos = nextPosition; Vector3 oldLastPos = lastPosition; nextPositionIndex = oldLastPosIndex; lastPositionIndex = oldNextPosIndex; nextPosition = oldLastPos; lastPosition = oldNextPos; } /// /// Does all the necessary calculations to set up the platform to go to the next position in the array. /// private void GoToNextPosition() { // Increment the next position index, and calculate the last position index nextPositionIndex++; lastPositionIndex = nextPositionIndex - 1; // Loop round to the beginning of the list if necessary nextPositionIndex %= numPositions; // Retrieve the positions from the array if (useRelativePositions) { // Adjust for relative positions lastPosition = originPosition + (originRotation * positions[lastPositionIndex]); nextPosition = originPosition + (originRotation * positions[nextPositionIndex]); } else { lastPosition = positions[lastPositionIndex]; nextPosition = positions[nextPositionIndex]; } // Calculate the travel distance between the two positions travelDistToNextPosition = (nextPosition - lastPosition).magnitude; // Calculate the travel time between the two positions travelTimeToNextPosition = averageMoveSpeed == 0f ? float.MaxValue : travelDistToNextPosition / averageMoveSpeed; // Reset the timers waitTimer = 0f; travelTimer = 0f; if (lastPositionIndex == 0) { // Start waiting if we have a wait timer for the first position if (startWaitTime > 0.001f) { isWaitingAtStartPosition = true; StopInTransitAudio(); PlayStartAudio(); if (onArriveStart != null) { onArriveStart.Invoke(true, false, platformId, Vector3.zero); } } else { // Arrive at the first position, then immediately depart if (onArriveStart != null) { onArriveStart.Invoke(true, false, platformId, Vector3.zero); } if (onDepartStart != null) { onDepartStart.Invoke(true, false, platformId, Vector3.zero); } } } else if (lastPositionIndex == numPositions - 1) { // Start waiting if we have a wait timer for the last position if (endWaitTime > 0.001f) { isWaitingAtEndPosition = true; StopInTransitAudio(); PlayEndAudio(); if (onArriveEnd != null) { onArriveEnd.Invoke(false, true, platformId, Vector3.zero); } } else { // Arrive at the last position, then immediately depart if (onArriveEnd != null) { onArriveEnd.Invoke(false, true, platformId, Vector3.zero); } if (onDepartEnd != null) { onDepartEnd.Invoke(false, true, platformId, Vector3.zero); } } } } private void MovePlatform() { // If we have a rigidbody and this is being called from FixedUpdate(), move the rigidbody rather than the transform. if (isRigidBody && isMoveFixedUpdate) { rBody.MovePosition(currentWorldPosition); } else { transform.position = currentWorldPosition; } } /// /// Attempt to play the InTransist audio clip /// private void PlayInTransitAudio() { if (isAudioAvailable && inTransitAudioClip != null) { platformAudio.clip = inTransitAudioClip; if (!platformAudio.isPlaying && platformAudio.isActiveAndEnabled) { platformAudio.Play(); } } } private void StopInTransitAudio() { if (isAudioAvailable && inTransitAudioClip != null) { platformAudio.volume = 0f; if (platformAudio.isActiveAndEnabled && platformAudio.isPlaying) { platformAudio.Stop(); } } } private void PlayStartAudio() { if (isAudioAvailable && audioArrivedStartClip != null) { platformAudio.volume = overallAudioVolume; platformAudio.PlayOneShot(audioArrivedStartClip, audioArrivedStartVolume); } } private void PlayEndAudio() { if (isAudioAvailable && audioArrivedEndClip != null) { platformAudio.volume = overallAudioVolume; platformAudio.PlayOneShot(audioArrivedEndClip, audioArrivedEndVolume); } } /// /// Calculates the time value for the current position in the movement profile curve. /// /// private float CalculateCurrentCurveTime () { // Position variables are in world space float distBetweenPositions = (nextPosition - lastPosition).magnitude; float distFromLast = (currentWorldPosition - lastPosition).magnitude; // Calculate normalised value how far between last and next position float distanceTravelledN = distBetweenPositions != 0f ? distFromLast / distBetweenPositions : 0f; // Conduct binary search to find at what time this normalised value occurs at float minTime = 0f; float maxTime = 1f; float timeEstimate = distanceTravelledN; float errorMargin = 0.01f; // Iterate a maximum of 10 times (for reasonable accuracy but not too high a cost) int maxSearchIterations = 10; for (int i = 0; i < maxSearchIterations; i++) { // Evaluate the curve at our current estimate float estimateValue = movementProfile.Evaluate(timeEstimate); // Check if our estimate is within the specified error margin float currentError = estimateValue - distanceTravelledN; if ((currentError > 0f && currentError < errorMargin) || (currentError < 0f && currentError > -errorMargin)) { // If our estimate is within the specified error margin, stop the search i = maxSearchIterations; } else { // If the evaluated value is too high, update our max time value if (estimateValue > distanceTravelledN) { maxTime = timeEstimate; } // If the evaluated value is too low, update our min time value else { minTime = timeEstimate; } // Calculate a new time estimate halfway between the min and max time values timeEstimate = (minTime + maxTime) * 0.5f; } } // Return the best estimate for the time scaled by travel time to next position return timeEstimate * travelTimeToNextPosition; } /// /// Calculates the approximate "speed" (the time derivative) of the movement profile curve at the specified time value. /// private float CalculateCurveSpeed (float currentCurveTime) { // Half of distance in t-direction to measure derivative over float tDist = 0.005f; // Calculate times to evaluate curve at float t1 = 0f; float t2 = 1f; if (currentCurveTime < tDist) { t1 = 0f; t2 = 2f * tDist; } else if (currentCurveTime > 1 - tDist) { t1 = 1 - (2f * tDist); t2 = 1f; } else { t1 = currentCurveTime - tDist; t2 = currentCurveTime + tDist; } // Evaluate curve and measure derivative float curveDerivative = ((movementProfile.Evaluate(t2) - movementProfile.Evaluate(t1)) * travelDistToNextPosition) / (2f * tDist * travelTimeToNextPosition); // Return derivative scaled by travel time to next position return curveDerivative / travelTimeToNextPosition; } #endregion Private Member Methods #region Public Member API Methods /// /// If the platform is travelling away from the start, stop it (using /// smooth stop if requested), then start it (using smooth start if /// requested), and move to the first or starting position. /// /// public void CallToStartPosition (bool useSmoothStartStop) { if (IsDestinationEnd()) { // Check if already at starting position if (isWaitingAtStartPosition) { // waiting at start so reset waiting timer to give more time to get onto platform waitTimer = 0f; } else { //Debug.Log("[DEBUG] SSCMovingPlatform.CallToStartPosition T:" + Time.time); isReversingDirection = true; StopPlatform(useSmoothStartStop); } } else if (isWaitingAtEndPosition) { // Get the lift moving if still waiting at the other end. // Too bad if someone or something is trying to get onto the platform... // Could have a mininum wait time if need be. waitTimer = endWaitTime; } } /// /// Initialises and resets the platform. Call this if you modify the positions list or if you want to reset the platform /// back to its original position and rotation. /// public void Initialise() { if (isInitialised) { return; } rBody = GetComponent(); isRigidBody = rBody != null; platformId = this.GetInstanceID(); if (isRigidBody) { // Kinematic does not support ContinuousDynamic. if (rBody.collisionDetectionMode == CollisionDetectionMode.ContinuousDynamic || rBody.collisionDetectionMode == CollisionDetectionMode.Continuous) { //rBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; rBody.collisionDetectionMode = CollisionDetectionMode.Discrete; } // If user hasn't selected an interpolation mode, select Interpolate. // NOTE: Other rigidbodies around the character will also need to use the same to avoid jitter. if (rBody.interpolation == RigidbodyInterpolation.None) { rBody.interpolation = RigidbodyInterpolation.Interpolate; } // Set up the rigidbody rBody.isKinematic = true; rBody.detectCollisions = true; } SetMoveUpdateType(moveUpdateType); currentWorldPosition = transform.position; currentWorldRotation = transform.rotation; previousWorldPosition = currentWorldPosition; SetOrigin(currentWorldPosition); // Used with relative positions originRotation = currentWorldRotation; // Check how many positions there are in the list numPositions = positions.Count; if (numPositions > 1) { // Set the position of the platform to the first position in the list. nextPositionIndex = 0; // Audio won't be played as ResetAudioSettings() is called later in Initialise(). // Typically you don't want a first posiiton audio clip to play when we first start. GoToNextPosition(); } else { #if UNITY_EDITOR Debug.LogWarning("ERROR: SSCMovingPlatform.Initialise - at least two positions are required for a moving platform."); #endif } // Set the position of the platform back to its starting rotation currentWorldRotation = Quaternion.Euler(startingRotation); transform.rotation = currentWorldRotation; ResetAudioSettings(); RefreshInverseCurve(); isInitialised = true; } /// /// If the platform is moving, is it travelling toward the end position? /// If the destination is the starting point, then this will return false. /// /// public bool IsDestinationEnd() { return lastPositionIndex < nextPositionIndex; } /// /// Set the world space origin of the platform. When useRelativePositions is true, all positions are relative to the origin. /// public void SetOrigin(Vector3 worldSpaceOrigin) { originPosition = worldSpaceOrigin; } /// /// Call this after changing the MoveProfile at runtime. /// public void RefreshInverseCurve() { invMovementProfile = SSCUtils.InverseAnimCurve(movementProfile); } /// /// Call this when you wish to remove any custom event listeners, like /// after creating them in code and then destroying the object. /// You could add this to your game play OnDestroy code. /// public void RemoveListeners() { if (isInitialised) { if (onArriveStart != null) { onArriveStart.RemoveAllListeners(); } if (onArriveEnd != null) { onArriveEnd.RemoveAllListeners(); } if (onDepartStart != null) { onDepartStart.RemoveAllListeners(); } if (onDepartEnd != null) { onDepartEnd.RemoveAllListeners(); } } } /// /// Call after changing audio source /// public void ResetAudioSettings() { isAudioAvailable = false; if (platformAudio != null) { platformAudio.volume = overallAudioVolume; isAudioAvailable = true; } } /// /// Set the update loop used to move and/or rotate the platform /// /// public void SetMoveUpdateType(MoveUpdateType newMoveUpdateType) { moveUpdateType = newMoveUpdateType; isMoveFixedUpdate = moveUpdateType == MoveUpdateType.FixedUpdate; } /// /// Start the platform either instantly or smoothly. /// It will be ignored if the platform is already starting. /// /// public void StartPlatform (bool isSmoothStart) { // Ensure platform is not in the process of starting, and that it is in fact not moving. if (!isSmoothStartEnabled && !move) { isSmoothStopEnabled = false; isSmoothStartEnabled = isSmoothStart; // Calculate the travel distance between the two positions travelDistToNextPosition = (nextPosition - lastPosition).magnitude; // Calculate the travel time between the two positions travelTimeToNextPosition = averageMoveSpeed == 0f ? float.MaxValue : travelDistToNextPosition / averageMoveSpeed; if (isSmoothStart && smoothStartTime > 0f) { // Smooth Start // Recalculate travel timer travelTimer = CalculateCurrentCurveTime(); // Set initial values for smooth movement smoothTimer = 0f; smoothAcceleration = averageMoveSpeed / smoothStartTime; smoothInitialPosition = currentWorldPosition; smoothInitialSpeed = 0f; move = true; } else { // Instant start (works for linear curves only) // Are we re-starting part way between locations? if (travelTimer != 0f || (!isWaitingAtStartPosition && !isWaitingAtEndPosition)) { // Recalculate travel timer travelTimer = CalculateCurrentCurveTime(); } move = true; } // No longer waiting at start or end waitTimer = 0f; isWaitingAtStartPosition = false; isWaitingAtEndPosition = false; } } /// /// Stop the platform either instantly or smoothly before the next position. /// Will be ignored if already stopping. /// /// public void StopPlatform (bool isSmoothStop) { // Ensure platform is not in the process of stopping, and that it is in fact moving. if (!isSmoothStopEnabled && move) { isSmoothStartEnabled = false; isSmoothStopEnabled = isSmoothStop; if (isSmoothStop && !isWaitingAtStartPosition && !isWaitingAtEndPosition && smoothStopTime > 0f) { smoothTimer = 0f; // deceleration (d) = (finalSpeed - initialSpeed) / time // Get current speed smoothInitialSpeed = (currentWorldPosition - previousWorldPosition).magnitude / Time.deltaTime; // Consider the distance to the next position and make sure we don't overshoot it. float stoppingTime = travelTimeToNextPosition - travelTimer < smoothStopTime ? travelTimeToNextPosition - travelTimer : smoothStopTime; // If we're already at or past the next position, stop now if (stoppingTime <= 0f) { move = false; } else { // Assume constant deceleration, so average speed is mean of initial and final speed // distance to stop = t * ((init speed + final speed) / 2) smoothDistanceToTravel = stoppingTime * (smoothInitialSpeed / 2f); //Debug.Log("[DEBUG] Distance To Stop: " + smoothDistanceToTravel); // Acceleration = - initSpeed / time (assuming constant deceleration and final speed is 0) smoothAcceleration = -smoothInitialSpeed / stoppingTime; smoothInitialPosition = currentWorldPosition; } } // Instant stop else { move = false; } isSmoothStopEnabled = move; // Switch the direction platform is moving if required // If smooth if (isReversingDirection && !isSmoothStopEnabled) { ChangeDirection(); isReversingDirection = false; //Debug.Log("[DEBUG] Stopped platform instantly - now should reverse the direction and start (smoothstart: " + isSmoothStartEnabled + ")"); // If smooth stop, assume a smooth start. StartPlatform(false); } } } /// /// Teleport the platform to new location by moving by an amount in the x, y and z directions. /// This could be useful if changing the origin or centre of your world to compensate for float-point error. /// /// public void TelePort(Vector3 delta) { transform.position += delta; currentWorldPosition = transform.position; SetOrigin(originPosition += delta); // For non-relative positions, move them all if (!useRelativePositions) { lastPosition += delta; nextPosition += delta; numPositions = positions.Count; for (int pIdx = 0; pIdx < numPositions; pIdx++) { positions[pIdx] += delta; } } } /// /// Update the current average move speed. Useful when the platform is already moving /// and you want to adjust the speed. /// /// public void UpdateAverageMoveSpeed(float newMoveSpeed) { // Calc normalised value how far between last and next position float distanceTravelledN = travelTimeToNextPosition == 0f ? 0f : travelTimer / travelTimeToNextPosition; averageMoveSpeed = newMoveSpeed; // Calculate the travel distance between the two positions travelDistToNextPosition = (nextPosition - lastPosition).magnitude; // Calculate the travel time between the two positions travelTimeToNextPosition = averageMoveSpeed == 0f ? float.MaxValue : travelDistToNextPosition / averageMoveSpeed; // Adjust travel timer travelTimer = travelTimeToNextPosition * distanceTravelledN; } #endregion Public Member Methods } }