title: Unity Multiplayer Car : networking particles and sound
body: <p>I am making a Unity game where the player is controlling a car and I want this game to be multiplayer. I am using the Standard Assets' Vehicule package for the car.</p>
  
  <p>The structure of the default car prefab for the controls is separated between a "User Control" script that handles the user inputs, and a "Car Controller" that handles the physics ( movements, acceleration, turning, steering etc).</p>
  
  <p>You can find below the sources of those two scripts, I just modified the CarUserControl.cs for networking.</p>
  
  <p>Networking position is not a problem and it works fine (I simply attached NetworkTransform component to the car and NetworkTransformChild to the wheels).</p>
  
  <p>The thing is that the Car Controller script also handles particle effects when the car is steering (smoke is generated and steering track appears), and sounds (acceleration, steering, skidding), and I have ABSOLUTELY NO IDEA on how to network that...</p>
  
  <p>So right now I have multiplayer car movement, but only the local car generates steering/skidding tracks, smoke, and sounds.</p>
  
  <p>Any idea on how to do it?</p>
  
  <p>Sources:</p>
  
  <p><strong>Modified CarUserInput.cs</strong> : added <code>NetworkBehavior</code> instead of <code>MonoBehavior</code> and added the <code>isLocalPlayer</code> check</p>
  
  <pre><code>using System;
  using UnityEngine;
  using UnityStandardAssets.CrossPlatformInput;
  using UnityEngine.Networking;
  
  namespace UnityStandardAssets.Vehicles.Car
  {
      [RequireComponent(typeof (CarController))]
      public class CarUserControl : NetworkBehaviour
      {
          private CarController m_Car; // the car controller we want to use
  
  
          private void Awake()
          {
              // get the car controller
              m_Car = GetComponent&lt;CarController&gt;();
          }
  
  
          private void FixedUpdate()
          {
              if (!isLocalPlayer)
                  return;
  
              // pass the input to the car!
              float h = CrossPlatformInputManager.GetAxis("Horizontal");
              float v = CrossPlatformInputManager.GetAxis("Vertical");
  #if !MOBILE_INPUT
              float handbrake = CrossPlatformInputManager.GetAxis("Jump");
              m_Car.Move(h, v, v, handbrake);
  #else
              m_Car.Move(h, v, v, 0f);
  #endif
          }
      }
  }
  </code></pre>
  
  <p><strong>Default CarController.cs:</strong></p>
  
  <pre><code>using System;
  using UnityEngine;
  
  namespace UnityStandardAssets.Vehicles.Car
  {
      internal enum CarDriveType
      {
          FrontWheelDrive,
          RearWheelDrive,
          FourWheelDrive
      }
  
      internal enum SpeedType
      {
          MPH,
          KPH
      }
  
      public class CarController : MonoBehaviour
      {
          [SerializeField] private CarDriveType m_CarDriveType = CarDriveType.FourWheelDrive;
          [SerializeField] private WheelCollider[] m_WheelColliders = new WheelCollider[4];
          [SerializeField] private GameObject[] m_WheelMeshes = new GameObject[4];
          [SerializeField] private WheelEffects[] m_WheelEffects = new WheelEffects[4];
          [SerializeField] private Vector3 m_CentreOfMassOffset;
          [SerializeField] private float m_MaximumSteerAngle;
          [Range(0, 1)] [SerializeField] private float m_SteerHelper; // 0 is raw physics , 1 the car will grip in the direction it is facing
          [Range(0, 1)] [SerializeField] private float m_TractionControl; // 0 is no traction control, 1 is full interference
          [SerializeField] private float m_FullTorqueOverAllWheels;
          [SerializeField] private float m_ReverseTorque;
          [SerializeField] private float m_MaxHandbrakeTorque;
          [SerializeField] private float m_Downforce = 100f;
          [SerializeField] private SpeedType m_SpeedType;
          [SerializeField] private float m_Topspeed = 200;
          [SerializeField] private static int NoOfGears = 5;
          [SerializeField] private float m_RevRangeBoundary = 1f;
          [SerializeField] private float m_SlipLimit;
          [SerializeField] private float m_BrakeTorque;
  
          private Quaternion[] m_WheelMeshLocalRotations;
          private Vector3 m_Prevpos, m_Pos;
          private float m_SteerAngle;
          private int m_GearNum;
          private float m_GearFactor;
          private float m_OldRotation;
          private float m_CurrentTorque;
          private Rigidbody m_Rigidbody;
          private const float k_ReversingThreshold = 0.01f;
  
          public bool Skidding { get; private set; }
          public float BrakeInput { get; private set; }
          public float CurrentSteerAngle{ get { return m_SteerAngle; }}
          public float CurrentSpeed{ get { return m_Rigidbody.velocity.magnitude*2.23693629f; }}
          public float MaxSpeed{get { return m_Topspeed; }}
          public float Revs { get; private set; }
          public float AccelInput { get; private set; }
  
          // Use this for initialization
          private void Start()
          {
              m_WheelMeshLocalRotations = new Quaternion[4];
              for (int i = 0; i &lt; 4; i++)
              {
                  m_WheelMeshLocalRotations[i] = m_WheelMeshes[i].transform.localRotation;
              }
              m_WheelColliders[0].attachedRigidbody.centerOfMass = m_CentreOfMassOffset;
  
              m_MaxHandbrakeTorque = float.MaxValue;
  
              m_Rigidbody = GetComponent&lt;Rigidbody&gt;();
              m_CurrentTorque = m_FullTorqueOverAllWheels - (m_TractionControl*m_FullTorqueOverAllWheels);
          }
  
  
          private void GearChanging()
          {
              float f = Mathf.Abs(CurrentSpeed/MaxSpeed);
              float upgearlimit = (1/(float) NoOfGears)*(m_GearNum + 1);
              float downgearlimit = (1/(float) NoOfGears)*m_GearNum;
  
              if (m_GearNum &gt; 0 &amp;&amp; f &lt; downgearlimit)
              {
                  m_GearNum--;
              }
  
              if (f &gt; upgearlimit &amp;&amp; (m_GearNum &lt; (NoOfGears - 1)))
              {
                  m_GearNum++;
              }
          }
  
  
          // simple function to add a curved bias towards 1 for a value in the 0-1 range
          private static float CurveFactor(float factor)
          {
              return 1 - (1 - factor)*(1 - factor);
          }
  
  
          // unclamped version of Lerp, to allow value to exceed the from-to range
          private static float ULerp(float from, float to, float value)
          {
              return (1.0f - value)*from + value*to;
          }
  
  
          private void CalculateGearFactor()
          {
              float f = (1/(float) NoOfGears);
              // gear factor is a normalised representation of the current speed within the current gear's range of speeds.
              // We smooth towards the 'target' gear factor, so that revs don't instantly snap up or down when changing gear.
              var targetGearFactor = Mathf.InverseLerp(f*m_GearNum, f*(m_GearNum + 1), Mathf.Abs(CurrentSpeed/MaxSpeed));
              m_GearFactor = Mathf.Lerp(m_GearFactor, targetGearFactor, Time.deltaTime*5f);
          }
  
  
          private void CalculateRevs()
          {
              // calculate engine revs (for display / sound)
              // (this is done in retrospect - revs are not used in force/power calculations)
              CalculateGearFactor();
              var gearNumFactor = m_GearNum/(float) NoOfGears;
              var revsRangeMin = ULerp(0f, m_RevRangeBoundary, CurveFactor(gearNumFactor));
              var revsRangeMax = ULerp(m_RevRangeBoundary, 1f, gearNumFactor);
              Revs = ULerp(revsRangeMin, revsRangeMax, m_GearFactor);
          }
  
  
          public void Move(float steering, float accel, float footbrake, float handbrake)
          {
              for (int i = 0; i &lt; 4; i++)
              {
                  Quaternion quat;
                  Vector3 position;
                  m_WheelColliders[i].GetWorldPose(out position, out quat);
                  m_WheelMeshes[i].transform.position = position;
                  m_WheelMeshes[i].transform.rotation = quat;
              }
  
              //clamp input values
              steering = Mathf.Clamp(steering, -1, 1);
              AccelInput = accel = Mathf.Clamp(accel, 0, 1);
              BrakeInput = footbrake = -1*Mathf.Clamp(footbrake, -1, 0);
              handbrake = Mathf.Clamp(handbrake, 0, 1);
  
              //Set the steer on the front wheels.
              //Assuming that wheels 0 and 1 are the front wheels.
              m_SteerAngle = steering*m_MaximumSteerAngle;
              m_WheelColliders[0].steerAngle = m_SteerAngle;
              m_WheelColliders[1].steerAngle = m_SteerAngle;
  
              SteerHelper();
              ApplyDrive(accel, footbrake);
              CapSpeed();
  
              //Set the handbrake.
              //Assuming that wheels 2 and 3 are the rear wheels.
              if (handbrake &gt; 0f)
              {
                  var hbTorque = handbrake*m_MaxHandbrakeTorque;
                  m_WheelColliders[2].brakeTorque = hbTorque;
                  m_WheelColliders[3].brakeTorque = hbTorque;
              }
  
  
              CalculateRevs();
              GearChanging();
  
              AddDownForce();
              CheckForWheelSpin();
              TractionControl();
          }
  
  
          private void CapSpeed()
          {
              float speed = m_Rigidbody.velocity.magnitude;
              switch (m_SpeedType)
              {
                  case SpeedType.MPH:
  
                      speed *= 2.23693629f;
                      if (speed &gt; m_Topspeed)
                          m_Rigidbody.velocity = (m_Topspeed/2.23693629f) * m_Rigidbody.velocity.normalized;
                      break;
  
                  case SpeedType.KPH:
                      speed *= 3.6f;
                      if (speed &gt; m_Topspeed)
                          m_Rigidbody.velocity = (m_Topspeed/3.6f) * m_Rigidbody.velocity.normalized;
                      break;
              }
          }
  
  
          private void ApplyDrive(float accel, float footbrake)
          {
  
              float thrustTorque;
              switch (m_CarDriveType)
              {
                  case CarDriveType.FourWheelDrive:
                      thrustTorque = accel * (m_CurrentTorque / 4f);
                      for (int i = 0; i &lt; 4; i++)
                      {
                          m_WheelColliders[i].motorTorque = thrustTorque;
                      }
                      break;
  
                  case CarDriveType.FrontWheelDrive:
                      thrustTorque = accel * (m_CurrentTorque / 2f);
                      m_WheelColliders[0].motorTorque = m_WheelColliders[1].motorTorque = thrustTorque;
                      break;
  
                  case CarDriveType.RearWheelDrive:
                      thrustTorque = accel * (m_CurrentTorque / 2f);
                      m_WheelColliders[2].motorTorque = m_WheelColliders[3].motorTorque = thrustTorque;
                      break;
  
              }
  
              for (int i = 0; i &lt; 4; i++)
              {
                  if (CurrentSpeed &gt; 5 &amp;&amp; Vector3.Angle(transform.forward, m_Rigidbody.velocity) &lt; 50f)
                  {
                      m_WheelColliders[i].brakeTorque = m_BrakeTorque*footbrake;
                  }
                  else if (footbrake &gt; 0)
                  {
                      m_WheelColliders[i].brakeTorque = 0f;
                      m_WheelColliders[i].motorTorque = -m_ReverseTorque*footbrake;
                  }
              }
          }
  
  
          private void SteerHelper()
          {
              for (int i = 0; i &lt; 4; i++)
              {
                  WheelHit wheelhit;
                  m_WheelColliders[i].GetGroundHit(out wheelhit);
                  if (wheelhit.normal == Vector3.zero)
                      return; // wheels arent on the ground so dont realign the rigidbody velocity
              }
  
              // this if is needed to avoid gimbal lock problems that will make the car suddenly shift direction
              if (Mathf.Abs(m_OldRotation - transform.eulerAngles.y) &lt; 10f)
              {
                  var turnadjust = (transform.eulerAngles.y - m_OldRotation) * m_SteerHelper;
                  Quaternion velRotation = Quaternion.AngleAxis(turnadjust, Vector3.up);
                  m_Rigidbody.velocity = velRotation * m_Rigidbody.velocity;
              }
              m_OldRotation = transform.eulerAngles.y;
          }
  
  
          // this is used to add more grip in relation to speed
          private void AddDownForce()
          {
              m_WheelColliders[0].attachedRigidbody.AddForce(-transform.up*m_Downforce*
                                                           m_WheelColliders[0].attachedRigidbody.velocity.magnitude);
          }
  
  
          // checks if the wheels are spinning and is so does three things
          // 1) emits particles
          // 2) plays tiure skidding sounds
          // 3) leaves skidmarks on the ground
          // these effects are controlled through the WheelEffects class
          private void CheckForWheelSpin()
          {
              // loop through all wheels
              for (int i = 0; i &lt; 4; i++)
              {
                  WheelHit wheelHit;
                  m_WheelColliders[i].GetGroundHit(out wheelHit);
  
                  // is the tire slipping above the given threshhold
                  if (Mathf.Abs(wheelHit.forwardSlip) &gt;= m_SlipLimit || Mathf.Abs(wheelHit.sidewaysSlip) &gt;= m_SlipLimit)
                  {
                      m_WheelEffects[i].EmitTyreSmoke();
  
                      // avoiding all four tires screeching at the same time
                      // if they do it can lead to some strange audio artefacts
                      if (!AnySkidSoundPlaying())
                      {
                          m_WheelEffects[i].PlayAudio();
                      }
                      continue;
                  }
  
                  // if it wasnt slipping stop all the audio
                  if (m_WheelEffects[i].PlayingAudio)
                  {
                      m_WheelEffects[i].StopAudio();
                  }
                  // end the trail generation
                  m_WheelEffects[i].EndSkidTrail();
              }
          }
  
          // crude traction control that reduces the power to wheel if the car is wheel spinning too much
          private void TractionControl()
          {
              WheelHit wheelHit;
              switch (m_CarDriveType)
              {
                  case CarDriveType.FourWheelDrive:
                      // loop through all wheels
                      for (int i = 0; i &lt; 4; i++)
                      {
                          m_WheelColliders[i].GetGroundHit(out wheelHit);
  
                          AdjustTorque(wheelHit.forwardSlip);
                      }
                      break;
  
                  case CarDriveType.RearWheelDrive:
                      m_WheelColliders[2].GetGroundHit(out wheelHit);
                      AdjustTorque(wheelHit.forwardSlip);
  
                      m_WheelColliders[3].GetGroundHit(out wheelHit);
                      AdjustTorque(wheelHit.forwardSlip);
                      break;
  
                  case CarDriveType.FrontWheelDrive:
                      m_WheelColliders[0].GetGroundHit(out wheelHit);
                      AdjustTorque(wheelHit.forwardSlip);
  
                      m_WheelColliders[1].GetGroundHit(out wheelHit);
                      AdjustTorque(wheelHit.forwardSlip);
                      break;
              }
          }
  
  
          private void AdjustTorque(float forwardSlip)
          {
              if (forwardSlip &gt;= m_SlipLimit &amp;&amp; m_CurrentTorque &gt;= 0)
              {
                  m_CurrentTorque -= 10 * m_TractionControl;
              }
              else
              {
                  m_CurrentTorque += 10 * m_TractionControl;
                  if (m_CurrentTorque &gt; m_FullTorqueOverAllWheels)
                  {
                      m_CurrentTorque = m_FullTorqueOverAllWheels;
                  }
              }
          }
  
  
          private bool AnySkidSoundPlaying()
          {
              for (int i = 0; i &lt; 4; i++)
              {
                  if (m_WheelEffects[i].PlayingAudio)
                  {
                      return true;
                  }
              }
              return false;
          }
      }
  }
  </code></pre>