Vehicle.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using AtomicEngine;
  5. public class Vehicle : CSComponent
  6. {
  7. private List<Wheel> _wheels = new List<Wheel>();
  8. private Input _input = AtomicNET.GetSubsystem<Input>();
  9. private RigidBody2D _rigidBody;
  10. private ParticleEmitter2D _exhaustParticles;
  11. private SoundSource3D _soundSource;
  12. private SoundSource3D _suspensionSoundSource;
  13. private Sound _accelSound;
  14. private Sound _brakeSound;
  15. private Sound[] _suspensionSounds;
  16. private int _horsePower;
  17. private int _maxSpdFwd;
  18. private int _maxSpdBwd;
  19. private int _rollForce;
  20. //private float lastSuspensionSound; //TODO there's no Time subsystem?
  21. private DateTime lastSuspensionSound = DateTime.Now;
  22. // We use this for the wheel logic
  23. private class Wheel
  24. {
  25. private readonly RigidBody2D _rigidBody;
  26. private readonly ConstraintWheel2D _constraint;
  27. private readonly ParticleEmitter2D _particleEmitter;
  28. private readonly float _particlesDistance;
  29. // This struct is used to pass wheel dynamics data
  30. public struct DynamicsData
  31. {
  32. public bool IsInContact;
  33. public float Resistance;
  34. public float AngularVelocity;
  35. }
  36. public Wheel(RigidBody2D rigidBody, ConstraintWheel2D constraint, ParticleEmitter2D particleEmitter, float particlesDistance)
  37. {
  38. _rigidBody = rigidBody; _constraint = constraint; _particleEmitter = particleEmitter; _particlesDistance = particlesDistance;
  39. }
  40. // Applies force and returns the work (normalized) necessary to reach the target speed
  41. public DynamicsData ApplyNonLinearTorque(int power, int targetSpeed, bool onlyInContact = false)
  42. {
  43. bool inContact = EmitSurfaceParticles();
  44. float fraction = _rigidBody.AngularVelocity/targetSpeed;
  45. float mult = fraction > 0 ? 1-fraction : 1;
  46. if (!onlyInContact || inContact)
  47. _rigidBody.ApplyTorque(mult*power, true);
  48. return new DynamicsData
  49. {
  50. Resistance = fraction*mult, AngularVelocity = _rigidBody.AngularVelocity, IsInContact = inContact
  51. };
  52. }
  53. // Emit particles if close to a surface point and returns true if positive
  54. public bool EmitSurfaceParticles()
  55. {
  56. Vector3 nearestSurfPoint = AtomicMain.GetSurfacePointClosestToPoint(_rigidBody.Node);
  57. float contactDistance = Vector3.Distance(_rigidBody.Node.Position, nearestSurfPoint);
  58. if (contactDistance > _particlesDistance)
  59. {
  60. _particleEmitter.Effect.StartColor = new Color(0, 0, 0, 0);
  61. return false;
  62. }
  63. _particleEmitter.Effect.StartColor = Color.White;
  64. _particleEmitter.Node.Position = nearestSurfPoint;
  65. return true;
  66. }
  67. // Returns current suspension compression for the wheel
  68. public float CurrentSuspensionCompression()
  69. {
  70. Vector2 anchorPosition = new Vector2(_constraint.OwnerBody.Node.WorldPosition +
  71. _constraint.OwnerBody.Node.WorldRotation*new Vector3(_constraint.Anchor));
  72. return Vector2.Distance(_constraint.OtherBody.Node.Position2D, anchorPosition);
  73. }
  74. }
  75. public Vehicle CreateChassis(Vector2 colliderCenter, float colliderRadius, int massDensity, Vector3 exhaustPosition,
  76. ParticleEffect2D exhaustParticles, Sound engineSound, Sound tireSound, Sound[] suspensionSounds,
  77. int horsePower, int maxSpeedFwd, int maxSpeedBwd, int rollForce)
  78. {
  79. // We set out private fields
  80. _horsePower = horsePower;
  81. _maxSpdFwd = maxSpeedFwd;
  82. _maxSpdBwd = maxSpeedBwd;
  83. _rollForce = rollForce;
  84. _rigidBody = GetComponent<RigidBody2D>();
  85. // We add the collider (circle collider at the moment)
  86. var col = AtomicMain.AddCollider<CollisionCircle2D>(Node, dens:massDensity, fric:0);
  87. col.SetRadius(colliderRadius);
  88. col.SetCenter(colliderCenter);
  89. // We create the exhaust particle system
  90. var exhaustParticlesNode = Node.CreateChild();
  91. exhaustParticlesNode.SetPosition(exhaustPosition);
  92. _exhaustParticles = exhaustParticlesNode.CreateComponent<ParticleEmitter2D>();
  93. _exhaustParticles.SetEffect(exhaustParticles);
  94. // We setup the engine sound and other sound effect
  95. engineSound.SetLooped(true);
  96. _soundSource = Node.CreateComponent<SoundSource3D>();
  97. _soundSource.SetNearDistance(10);
  98. _soundSource.SetFarDistance(50);
  99. _accelSound = engineSound;
  100. _brakeSound = tireSound;
  101. _suspensionSoundSource = Node.CreateComponent<SoundSource3D>();
  102. _suspensionSounds = suspensionSounds;
  103. // We return the Vehicle for convenience, since this function is intended to be the vehicle's init function
  104. return this;
  105. }
  106. public Node CreateWheel(Sprite2D sprite, Vector2 relativePosition, float radius, int suspensionFrequency, float suspensionDamping,
  107. ParticleEffect2D particles, float distanceToEmitParticles)
  108. {
  109. Node wheelNode = AtomicMain.CreateSpriteNode(sprite);
  110. wheelNode.SetPosition2D(relativePosition);
  111. // CreateSpriteNode adds a RigidBody for us, so we get it here
  112. RigidBody2D wheelRigidBody = wheelNode.GetComponent<RigidBody2D>();
  113. // We activate CCD
  114. wheelRigidBody.SetBullet(true);
  115. AtomicMain.AddCollider<CollisionCircle2D>(wheelNode).SetRadius(radius);
  116. // The Box2D wheel joint provides spring for simulating suspension
  117. ConstraintWheel2D wheelJoint = Node.CreateComponent<ConstraintWheel2D>();
  118. wheelJoint.SetOtherBody(wheelRigidBody);
  119. wheelJoint.SetAnchor(relativePosition);
  120. wheelJoint.SetAxis(Vector2.UnitY);
  121. wheelJoint.SetFrequencyHz(suspensionFrequency);
  122. wheelJoint.SetDampingRatio(suspensionDamping);
  123. // Each wheel has a particle emitter to emit particles when it's in contact with the surface
  124. Node particlesNode = Node.Scene.CreateChild();
  125. particlesNode.SetPosition(new Vector3(relativePosition.X, relativePosition.Y, 14));
  126. ParticleEmitter2D particleEmitter = particlesNode.CreateComponent<ParticleEmitter2D>();
  127. particleEmitter.SetEffect(particles);
  128. // We create a new Wheel struct and add to the _wheels list
  129. _wheels.Add(new Wheel(wheelRigidBody, wheelJoint, particleEmitter, distanceToEmitParticles));
  130. return wheelNode;
  131. }
  132. public Node CreateHead(Sprite2D sprite, Vector3 relativePosition, float colliderRadius, Vector2 neckAnchor)
  133. {
  134. Node head = AtomicMain.CreateSpriteNode(sprite);
  135. head.SetPosition(relativePosition);
  136. AtomicMain.AddCollider<CollisionCircle2D>(head).SetRadius(colliderRadius);
  137. // This is the actual neck joint
  138. ConstraintRevolute2D joint = head.CreateComponent<ConstraintRevolute2D>();
  139. joint.SetOtherBody(_rigidBody);
  140. joint.SetAnchor(neckAnchor);
  141. // This is the spring, it's attached to the body with an offset
  142. ConstraintDistance2D spring = head.CreateComponent<ConstraintDistance2D>();
  143. spring.SetOtherBody(_rigidBody);
  144. spring.SetOwnerBodyAnchor(-Vector2.UnitY*2);
  145. spring.SetOtherBodyAnchor(Node.WorldToLocal2D(head.WorldPosition2D-Vector2.UnitY*2));
  146. spring.SetFrequencyHz(3);
  147. spring.SetDampingRatio(0.4f);
  148. return head;
  149. }
  150. // Update is called once per frame
  151. private void Update(float dt)
  152. {
  153. // Wheel controls
  154. bool isBraking = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_DOWN);
  155. bool isAccelerating = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_UP);
  156. foreach (Wheel wheel in _wheels)
  157. {
  158. // We give priority to braking
  159. if (isBraking)
  160. {
  161. // This function also emit particles
  162. Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(_horsePower, _maxSpdBwd, true);
  163. if (wheelDynamics.IsInContact)
  164. {
  165. // If the wheel is in contact, we play the braking sound according to the current work in the wheel
  166. if (_soundSource.Sound != _brakeSound || !_soundSource.IsPlaying())
  167. _soundSource.Play(_brakeSound);
  168. _soundSource.SetFrequency(48000);
  169. _soundSource.Gain = wheelDynamics.Resistance*-1;
  170. if (_soundSource.Gain > 1) _soundSource.Gain = 1;
  171. }
  172. else
  173. {
  174. _soundSource.Stop();
  175. }
  176. }
  177. else if (isAccelerating)
  178. {
  179. Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(-_horsePower, -_maxSpdFwd);
  180. // We set the sound frequency according to the speed and gain according to the work being done
  181. _soundSource.SetFrequency((wheelDynamics.AngularVelocity/-40+1)*48000);
  182. _soundSource.Gain += 0.03f;
  183. if (_soundSource.Gain > 1) _soundSource.Gain = 1;
  184. _soundSource.Gain += Math.Abs(1-wheelDynamics.Resistance*5);
  185. if (_soundSource.Sound != _accelSound || !_soundSource.IsPlaying())
  186. _soundSource.Play(_accelSound);
  187. }
  188. else
  189. {
  190. // If it's not receiving any input, fades out engine sound or stop other sounds
  191. if (_soundSource.Sound == _accelSound)
  192. {
  193. _soundSource.SetFrequency(48000);
  194. _soundSource.Gain -= 0.01f;
  195. if (_soundSource.Gain < 0.3f) _soundSource.Gain = 0.3f;
  196. }
  197. else
  198. _soundSource.Stop();
  199. // We emit surface particles anyway
  200. wheel.EmitSurfaceParticles();
  201. }
  202. }
  203. // Roll controls
  204. if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_LEFT))
  205. _rigidBody.ApplyTorque(_rollForce,true);
  206. if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_RIGHT))
  207. _rigidBody.ApplyTorque(_rollForce*-1,true);
  208. // Debug control for when you rolled over
  209. if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_SPACE))
  210. _rigidBody.SetAngularVelocity(2);
  211. // We apply bit of the vehicle's velocity to the exhaust particles so they look OK
  212. Vector2 currentVelocity = _rigidBody.GetLinearVelocity();
  213. _exhaustParticles.Effect.Speed = Math.Abs(100 + currentVelocity.LengthFast * 20);
  214. _exhaustParticles.Effect.SpeedVariance = currentVelocity.LengthFast * 10;
  215. _exhaustParticles.Effect.Angle = -Node.Rotation2D +
  216. (float)(Math.Atan2(currentVelocity.X - 5, -currentVelocity.Y) * (360 / (Math.PI * 2))) - 90;
  217. // Suspension sounds
  218. foreach (Wheel wheel in _wheels)
  219. {
  220. if (wheel.CurrentSuspensionCompression() > 0.7f)
  221. {
  222. if (DateTime.Now - lastSuspensionSound > TimeSpan.FromMilliseconds(500))
  223. {
  224. lastSuspensionSound=DateTime.Now;
  225. _suspensionSoundSource.Play(_suspensionSounds[new Random().Next(_suspensionSounds.Length)]);
  226. }
  227. }
  228. }
  229. }
  230. }