Newer
Older
CGTrack / Assets / Oculus / SampleFramework / Usage / HandsTrainExample / Scripts / TrainLocomotive.cs
/************************************************************************************

See SampleFramework license.txt for license terms.  Unless required by applicable law 
or agreed to in writing, the sample code is provided “AS IS” WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied.  See the license for specific 
language governing permissions and limitations under the license.

************************************************************************************/

using System.Collections;
using UnityEngine;
using UnityEngine.Assertions;

namespace OculusSampleFramework
{
	public class TrainLocomotive : TrainCarBase
	{
		private const float MIN_SPEED = 0.2f;
		private const float MAX_SPEED = 2.7f;
		private const float SMOKE_SPEED_MULTIPLIER = 8f;
		private const int MAX_PARTICLES_MULTIPLIER = 3;

		private enum EngineSoundState
		{
			Start = 0,
			AccelerateOrSetProperSpeed,
			Stop
		};

		[SerializeField]
		[Range(MIN_SPEED, MAX_SPEED)]
		protected float _initialSpeed = 0f;

		[SerializeField] private GameObject _startStopButton = null;
		[SerializeField] private GameObject _decreaseSpeedButton = null;
		[SerializeField] private GameObject _increaseSpeedButton = null;
		[SerializeField] private GameObject _smokeButton = null;
		[SerializeField] private GameObject _whistleButton = null;
		[SerializeField] private GameObject _reverseButton = null;
		[SerializeField] private AudioSource _whistleAudioSource = null;
		[SerializeField] private AudioClip _whistleSound = null;
		[SerializeField] private AudioSource _engineAudioSource = null;
		[SerializeField] private AudioClip[] _accelerationSounds = null;
		[SerializeField] private AudioClip[] _decelerationSounds = null;
		[SerializeField] private AudioClip _startUpSound = null;

		[SerializeField] private AudioSource _smokeStackAudioSource = null;
		[SerializeField] private AudioClip _smokeSound = null;

		[SerializeField] private ParticleSystem _smoke1 = null;
		[SerializeField] private ParticleSystem _smoke2 = null;
		[SerializeField] private TrainCarBase[] _childCars = null;

		private bool _isMoving = true;
		private bool _reverse = false;
		private float _currentSpeed, _speedDiv;

		private float _standardRateOverTimeMultiplier;
		private int _standardMaxParticles;

		private Coroutine _startStopTrainCr;

		private void Start()
		{
			Assert.IsNotNull(_startStopButton);
			Assert.IsNotNull(_decreaseSpeedButton);
			Assert.IsNotNull(_increaseSpeedButton);
			Assert.IsNotNull(_smokeButton);
			Assert.IsNotNull(_whistleButton);
			Assert.IsNotNull(_reverseButton);
			Assert.IsNotNull(_whistleAudioSource);
			Assert.IsNotNull(_whistleSound);
			Assert.IsNotNull(_smoke1);

			Assert.IsNotNull(_engineAudioSource);
			Assert.IsNotNull(_accelerationSounds);
			Assert.IsNotNull(_decelerationSounds);
			Assert.IsNotNull(_startUpSound);

			Assert.IsNotNull(_smokeStackAudioSource);
			Assert.IsNotNull(_smokeSound);


			_standardRateOverTimeMultiplier = _smoke1.emission.rateOverTimeMultiplier;
			_standardMaxParticles = _smoke1.main.maxParticles;

			Distance = 0.0f;
			_speedDiv = (MAX_SPEED - MIN_SPEED) / _accelerationSounds.Length;
			_currentSpeed = _initialSpeed;
			UpdateCarPosition();

			_smoke1.Stop();
			_startStopTrainCr = StartCoroutine(StartStopTrain(true));
		}

		private void Update()
		{
			UpdatePosition();
		}

		public override void UpdatePosition()
		{
			if (!_isMoving)
			{
				return;
			}

			if (_trainTrack != null)
			{
				UpdateDistance();
				UpdateCarPosition();
				RotateCarWheels();
			}

			foreach (var trainCarBase in _childCars)
			{
				trainCarBase.UpdatePosition();
			}
		}

		public void StartStopStateChanged()
		{
			if (_startStopTrainCr == null)
			{
				_startStopTrainCr = StartCoroutine(StartStopTrain(!_isMoving));
			}
		}

		private IEnumerator StartStopTrain(bool startTrain)
		{
			float endSpeed = startTrain ? _initialSpeed : 0.0f;

			var timePeriodForSpeedChange = 3.0f;
			if (startTrain)
			{
				_smoke1.Play();
				_isMoving = true;
				var emissionModule1 = _smoke1.emission;
				var mainModule = _smoke1.main;
				emissionModule1.rateOverTimeMultiplier = _standardRateOverTimeMultiplier;
				mainModule.maxParticles = _standardMaxParticles;
				timePeriodForSpeedChange = PlayEngineSound(EngineSoundState.Start);
			}
			else
			{
				timePeriodForSpeedChange = PlayEngineSound(EngineSoundState.Stop);
			}

			// don't loop audio at first; only do when train continues movement below
			_engineAudioSource.loop = false;

			// make time period a tad shorter so that if it's not looping, we don't
			// catch the beginning of the sound
			timePeriodForSpeedChange = timePeriodForSpeedChange * 0.9f;
			float startTime = Time.time;
			float endTime = Time.time + timePeriodForSpeedChange;
			float startSpeed = _currentSpeed;
			while (Time.time < endTime)
			{
				float t = (Time.time - startTime) / timePeriodForSpeedChange;
				_currentSpeed = startSpeed * (1.0f - t) + endSpeed * t;
				UpdateSmokeEmissionBasedOnSpeed();
				yield return null;
			}

			_currentSpeed = endSpeed;
			_startStopTrainCr = null;
			_isMoving = startTrain;
			if (!_isMoving)
			{
				_smoke1.Stop();
			}
			else
			{
				_engineAudioSource.loop = true;
				PlayEngineSound(EngineSoundState.AccelerateOrSetProperSpeed);
			}
		}

		private float PlayEngineSound(EngineSoundState engineSoundState)
		{
			AudioClip audioClip = null;

			if (engineSoundState == EngineSoundState.Start)
			{
				audioClip = _startUpSound;
			}
			else
			{
				AudioClip[] audioClips = engineSoundState == EngineSoundState.AccelerateOrSetProperSpeed
					? _accelerationSounds
					: _decelerationSounds;
				int numSounds = audioClips.Length;
				int speedIndex = (int)Mathf.Round((_currentSpeed - MIN_SPEED) / _speedDiv);
				audioClip = audioClips[Mathf.Clamp(speedIndex, 0, numSounds - 1)];
			}

			// if audio is already playing and we are playing the same track, don't interrupt it
			if (_engineAudioSource.clip == audioClip && _engineAudioSource.isPlaying &&
				engineSoundState == EngineSoundState.AccelerateOrSetProperSpeed)
			{
				return 0.0f;
			}

			_engineAudioSource.clip = audioClip;
			_engineAudioSource.timeSamples = 0;
			_engineAudioSource.Play();

			return audioClip.length;
		}

		private void UpdateDistance()
		{
			var signedSpeed = _reverse ? -_currentSpeed : _currentSpeed;
			Distance = (Distance + signedSpeed * Time.deltaTime) % _trainTrack.TrackLength;
		}

		public void DecreaseSpeedStateChanged()
		{
			if (_startStopTrainCr == null && _isMoving)
			{
				_currentSpeed = Mathf.Clamp(_currentSpeed - _speedDiv, MIN_SPEED, MAX_SPEED);
				UpdateSmokeEmissionBasedOnSpeed();
				PlayEngineSound(EngineSoundState.AccelerateOrSetProperSpeed);
			}
		}

		public void IncreaseSpeedStateChanged()
		{
			if (_startStopTrainCr == null && _isMoving)
			{
				_currentSpeed = Mathf.Clamp(_currentSpeed + _speedDiv, MIN_SPEED, MAX_SPEED);
				UpdateSmokeEmissionBasedOnSpeed();
				PlayEngineSound(EngineSoundState.AccelerateOrSetProperSpeed);
			}
		}

		private void UpdateSmokeEmissionBasedOnSpeed()
		{
			var emissionModule = _smoke1.emission;
			emissionModule.rateOverTimeMultiplier = GetCurrentSmokeEmission();
			var mainModule = _smoke1.main;
			mainModule.maxParticles = (int)Mathf.Lerp(_standardMaxParticles, _standardMaxParticles * MAX_PARTICLES_MULTIPLIER,
				_currentSpeed / (MAX_SPEED - MIN_SPEED));
		}

		private float GetCurrentSmokeEmission()
		{
			return Mathf.Lerp(_standardRateOverTimeMultiplier, _standardRateOverTimeMultiplier * SMOKE_SPEED_MULTIPLIER,
				_currentSpeed / (MAX_SPEED - MIN_SPEED));
		}

		public void SmokeButtonStateChanged()
		{
			if (_isMoving)
			{
				_smokeStackAudioSource.clip = _smokeSound;
				_smokeStackAudioSource.timeSamples = 0;
				_smokeStackAudioSource.Play();

				_smoke2.time = 0.0f;
				_smoke2.Play();
			}
		}

		public void WhistleButtonStateChanged()
		{
			if (_whistleSound != null)
			{
				_whistleAudioSource.clip = _whistleSound;
				_whistleAudioSource.timeSamples = 0;
				_whistleAudioSource.Play();
			}
		}

		public void ReverseButtonStateChanged()
		{
			_reverse = !_reverse;
		}
	}
}