| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using AtomicEngine;
- public class Vehicle : CSComponent
- {
- private List<Wheel> _wheels = new List<Wheel>();
- private Input _input = AtomicNET.GetSubsystem<Input>();
- private RigidBody2D _rigidBody;
- private ParticleEmitter2D _exhaustParticles;
- private SoundSource3D _soundSource;
- private SoundSource3D _suspensionSoundSource;
- private Sound _accelSound;
- private Sound _brakeSound;
- private Sound[] _suspensionSounds;
- private int _horsePower;
- private int _maxSpdFwd;
- private int _maxSpdBwd;
- private int _rollForce;
- //private float lastSuspensionSound; //TODO there's no Time subsystem?
- private DateTime lastSuspensionSound = DateTime.Now;
- // We use this for the wheel logic
- private class Wheel
- {
- private readonly RigidBody2D _rigidBody;
- private readonly ConstraintWheel2D _constraint;
- private readonly ParticleEmitter2D _particleEmitter;
- private readonly float _particlesDistance;
- // This struct is used to pass wheel dynamics data
- public struct DynamicsData
- {
- public bool IsInContact;
- public float Resistance;
- public float AngularVelocity;
- }
- public Wheel(RigidBody2D rigidBody, ConstraintWheel2D constraint, ParticleEmitter2D particleEmitter, float particlesDistance)
- {
- _rigidBody = rigidBody; _constraint = constraint; _particleEmitter = particleEmitter; _particlesDistance = particlesDistance;
- }
- // Applies force and returns the work (normalized) necessary to reach the target speed
- public DynamicsData ApplyNonLinearTorque(int power, int targetSpeed, bool onlyInContact = false)
- {
- bool inContact = EmitSurfaceParticles();
- float fraction = _rigidBody.AngularVelocity/targetSpeed;
- float mult = fraction > 0 ? 1-fraction : 1;
- if (!onlyInContact || inContact)
- _rigidBody.ApplyTorque(mult*power, true);
- return new DynamicsData
- {
- Resistance = fraction*mult, AngularVelocity = _rigidBody.AngularVelocity, IsInContact = inContact
- };
- }
- // Emit particles if close to a surface point and returns true if positive
- public bool EmitSurfaceParticles()
- {
- Vector3 nearestSurfPoint = AtomicMain.GetSurfacePointClosestToPoint(_rigidBody.Node);
- float contactDistance = Vector3.Distance(_rigidBody.Node.Position, nearestSurfPoint);
- if (contactDistance > _particlesDistance)
- {
- _particleEmitter.Effect.StartColor = new Color(0, 0, 0, 0);
- return false;
- }
- _particleEmitter.Effect.StartColor = Color.White;
- _particleEmitter.Node.Position = nearestSurfPoint;
- return true;
- }
- // Returns current suspension compression for the wheel
- public float CurrentSuspensionCompression()
- {
- Vector2 anchorPosition = new Vector2(_constraint.OwnerBody.Node.WorldPosition +
- _constraint.OwnerBody.Node.WorldRotation*new Vector3(_constraint.Anchor));
- return Vector2.Distance(_constraint.OtherBody.Node.Position2D, anchorPosition);
- }
- }
-
- public Vehicle CreateChassis(Vector2 colliderCenter, float colliderRadius, int massDensity, Vector3 exhaustPosition,
- ParticleEffect2D exhaustParticles, Sound engineSound, Sound tireSound, Sound[] suspensionSounds,
- int horsePower, int maxSpeedFwd, int maxSpeedBwd, int rollForce)
- {
- // We set out private fields
- _horsePower = horsePower;
- _maxSpdFwd = maxSpeedFwd;
- _maxSpdBwd = maxSpeedBwd;
- _rollForce = rollForce;
- _rigidBody = GetComponent<RigidBody2D>();
- // We add the collider (circle collider at the moment)
- var col = AtomicMain.AddCollider<CollisionCircle2D>(Node, dens:massDensity, fric:0);
- col.SetRadius(colliderRadius);
- col.SetCenter(colliderCenter);
-
- // We create the exhaust particle system
- var exhaustParticlesNode = Node.CreateChild();
- exhaustParticlesNode.SetPosition(exhaustPosition);
- _exhaustParticles = exhaustParticlesNode.CreateComponent<ParticleEmitter2D>();
- _exhaustParticles.SetEffect(exhaustParticles);
-
- // We setup the engine sound and other sound effect
- engineSound.SetLooped(true);
- _soundSource = Node.CreateComponent<SoundSource3D>();
- _soundSource.SetNearDistance(10);
- _soundSource.SetFarDistance(50);
- _accelSound = engineSound;
- _brakeSound = tireSound;
- _suspensionSoundSource = Node.CreateComponent<SoundSource3D>();
- _suspensionSounds = suspensionSounds;
- // We return the Vehicle for convenience, since this function is intended to be the vehicle's init function
- return this;
- }
- public Node CreateWheel(Sprite2D sprite, Vector2 relativePosition, float radius, int suspensionFrequency, float suspensionDamping,
- ParticleEffect2D particles, float distanceToEmitParticles)
- {
- Node wheelNode = AtomicMain.CreateSpriteNode(sprite);
- wheelNode.SetPosition2D(relativePosition);
- // CreateSpriteNode adds a RigidBody for us, so we get it here
- RigidBody2D wheelRigidBody = wheelNode.GetComponent<RigidBody2D>();
- // We activate CCD
- wheelRigidBody.SetBullet(true);
- AtomicMain.AddCollider<CollisionCircle2D>(wheelNode).SetRadius(radius);
-
- // The Box2D wheel joint provides spring for simulating suspension
- ConstraintWheel2D wheelJoint = Node.CreateComponent<ConstraintWheel2D>();
- wheelJoint.SetOtherBody(wheelRigidBody);
- wheelJoint.SetAnchor(relativePosition);
- wheelJoint.SetAxis(Vector2.UnitY);
- wheelJoint.SetFrequencyHz(suspensionFrequency);
- wheelJoint.SetDampingRatio(suspensionDamping);
- // Each wheel has a particle emitter to emit particles when it's in contact with the surface
- Node particlesNode = Node.Scene.CreateChild();
- particlesNode.SetPosition(new Vector3(relativePosition.X, relativePosition.Y, 14));
- ParticleEmitter2D particleEmitter = particlesNode.CreateComponent<ParticleEmitter2D>();
- particleEmitter.SetEffect(particles);
- // We create a new Wheel struct and add to the _wheels list
- _wheels.Add(new Wheel(wheelRigidBody, wheelJoint, particleEmitter, distanceToEmitParticles));
- return wheelNode;
- }
- public Node CreateHead(Sprite2D sprite, Vector3 relativePosition, float colliderRadius, Vector2 neckAnchor)
- {
- Node head = AtomicMain.CreateSpriteNode(sprite);
- head.SetPosition(relativePosition);
- AtomicMain.AddCollider<CollisionCircle2D>(head).SetRadius(colliderRadius);
- // This is the actual neck joint
- ConstraintRevolute2D joint = head.CreateComponent<ConstraintRevolute2D>();
- joint.SetOtherBody(_rigidBody);
- joint.SetAnchor(neckAnchor);
-
- // This is the spring, it's attached to the body with an offset
- ConstraintDistance2D spring = head.CreateComponent<ConstraintDistance2D>();
- spring.SetOtherBody(_rigidBody);
- spring.SetOwnerBodyAnchor(-Vector2.UnitY*2);
- spring.SetOtherBodyAnchor(Node.WorldToLocal2D(head.WorldPosition2D-Vector2.UnitY*2));
- spring.SetFrequencyHz(3);
- spring.SetDampingRatio(0.4f);
- return head;
- }
- // Update is called once per frame
- private void Update(float dt)
- {
- // Wheel controls
- bool isBraking = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_DOWN);
- bool isAccelerating = _input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_UP);
-
- foreach (Wheel wheel in _wheels)
- {
- // We give priority to braking
- if (isBraking)
- {
- // This function also emit particles
- Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(_horsePower, _maxSpdBwd, true);
- if (wheelDynamics.IsInContact)
- {
- // If the wheel is in contact, we play the braking sound according to the current work in the wheel
- if (_soundSource.Sound != _brakeSound || !_soundSource.IsPlaying())
- _soundSource.Play(_brakeSound);
- _soundSource.SetFrequency(48000);
- _soundSource.Gain = wheelDynamics.Resistance*-1;
- if (_soundSource.Gain > 1) _soundSource.Gain = 1;
- }
- else
- {
- _soundSource.Stop();
- }
- }
- else if (isAccelerating)
- {
- Wheel.DynamicsData wheelDynamics = wheel.ApplyNonLinearTorque(-_horsePower, -_maxSpdFwd);
- // We set the sound frequency according to the speed and gain according to the work being done
- _soundSource.SetFrequency((wheelDynamics.AngularVelocity/-40+1)*48000);
- _soundSource.Gain += 0.03f;
- if (_soundSource.Gain > 1) _soundSource.Gain = 1;
- _soundSource.Gain += Math.Abs(1-wheelDynamics.Resistance*5);
- if (_soundSource.Sound != _accelSound || !_soundSource.IsPlaying())
- _soundSource.Play(_accelSound);
- }
- else
- {
- // If it's not receiving any input, fades out engine sound or stop other sounds
- if (_soundSource.Sound == _accelSound)
- {
- _soundSource.SetFrequency(48000);
- _soundSource.Gain -= 0.01f;
- if (_soundSource.Gain < 0.3f) _soundSource.Gain = 0.3f;
- }
- else
- _soundSource.Stop();
- // We emit surface particles anyway
- wheel.EmitSurfaceParticles();
- }
- }
- // Roll controls
- if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_LEFT))
- _rigidBody.ApplyTorque(_rollForce,true);
- if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_RIGHT))
- _rigidBody.ApplyTorque(_rollForce*-1,true);
- // Debug control for when you rolled over
- if (_input.GetKeyDown((int) SDL.SDL_Keycode.SDLK_SPACE))
- _rigidBody.SetAngularVelocity(2);
- // We apply bit of the vehicle's velocity to the exhaust particles so they look OK
- Vector2 currentVelocity = _rigidBody.GetLinearVelocity();
- _exhaustParticles.Effect.Speed = Math.Abs(100 + currentVelocity.LengthFast * 20);
- _exhaustParticles.Effect.SpeedVariance = currentVelocity.LengthFast * 10;
- _exhaustParticles.Effect.Angle = -Node.Rotation2D +
- (float)(Math.Atan2(currentVelocity.X - 5, -currentVelocity.Y) * (360 / (Math.PI * 2))) - 90;
- // Suspension sounds
- foreach (Wheel wheel in _wheels)
- {
- if (wheel.CurrentSuspensionCompression() > 0.7f)
- {
- if (DateTime.Now - lastSuspensionSound > TimeSpan.FromMilliseconds(500))
- {
- lastSuspensionSound=DateTime.Now;
- _suspensionSoundSource.Play(_suspensionSounds[new Random().Next(_suspensionSounds.Length)]);
- }
- }
- }
- }
- }
|