using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class ShipControls : MonoBehaviour { public Camera aim; public Timing timing; ShipAudio shipAudio; SpaceRaceInput input; Rigidbody rigidBody; Vector3 initialPosition; Vector3 initialForce; Quaternion initialAim; ForceMode forceMode; Vector3 thrust; Vector3 strafe; // Camera Controls float camPitch; float camYaw; float clampAngle = 90.0f; float _invertY = 1.0f; public bool InvertY { set { _invertY = value ? -1.0f : 1.0f; Debug.Log($"Set invert Y {_invertY}"); } } float _viewSens = 1.0f; public float ViewSens { get => _viewSens; set { _viewSens = value; Debug.Log($"Set view sensitivity {_viewSens}"); } } bool _muteAudio = false; public bool MuteAudio { set { _muteAudio = value; AudioListener.volume = _muteAudio ? 0f : 1f; Debug.Log($"Set muteAudio {_muteAudio}"); } } public PowerUnit powerUnit = new(); public float Speed => rigidBody.velocity.magnitude; // used for audio public bool Accelerating => input.ship.accelerate.ReadValue() > 0; public bool Decelerating => input.ship.decelerate.ReadValue() > 0; void Start() { Cursor.lockState = CursorLockMode.Locked; rigidBody = GetComponent(); shipAudio = GetComponentInChildren(); // just stop the body from being affected by rotation for now // until the 3p camera is in to explain why rigidBody.freezeRotation = true; initialPosition = rigidBody.transform.position; initialAim = rigidBody.transform.rotation; initialForce = Vector3.forward; thrust = Vector3.zero; strafe = Vector3.zero; camPitch = initialAim.eulerAngles.x; camYaw = initialAim.eulerAngles.y; forceMode = ForceMode.Acceleration; SetupActions(); Respawn(); } // to minimise input lag we poll for the thrust vector every frame and set it // same with the camera, we always look around void Update() { // hot reloading fks up the input if (input == null) Start(); UpdateCamera(); thrust = getThrust(); } void FixedUpdate() { // when you hit the trigger... GO // needs to be done here in fixedUpdate because otherwise the input is checked on every frame // so they will have already hit the button before this tick and they actually start moving // aka ripoff bool shouldBegin = !timing.timerEnabled && (thrust != Vector3.zero || strafe != Vector3.zero); if (shouldBegin) Begin(); // the ship being propelled forward // this is in world space because of the xform from the camera pos // rigidBody.AddForce(thrust, forceMode); // moving side to side // this is relative because it's easy to just multiply by the strafe vector rigidBody.AddRelativeForce(strafe, forceMode); // little check to stop the ship bouncing between forward and back when holding decel if (rigidBody.velocity.magnitude < 0.2) rigidBody.velocity = Vector3.zero; // rotating towards the camera's orientation // not being done by a force because of strange behaviour // needs investigation float step = powerUnit.Stabilisation * Time.fixedDeltaTime; // stop the ship jiggling because of micro camera movement if (Quaternion.Angle(aim.transform.rotation, rigidBody.rotation) > 1f) rigidBody.rotation = Quaternion.RotateTowards(transform.rotation, aim.transform.rotation, step); if (timing.timerEnabled) { int boostBefore = powerUnit.BoostChargesAvailable; powerUnit.Tick(); if (powerUnit.BoostChargesAvailable > boostBefore) OnBoostCharge(); } } void UpdateCamera() { // there's some hokey scaling that went on with the old input // we were originally using // in the action mapping the mouse is scaled by 0.05 // delta *= 0.5f; // Account for scaling applied directly in Windows code by old input system. // delta *= 0.1f; // Account for sensitivity setting on old Mouse X and Y axes. // def needs to be configurable Vector2 lookDelta = input.ship.look.ReadValue(); camYaw += lookDelta.x * _viewSens; camPitch -= lookDelta.y * _viewSens * _invertY; camPitch = Mathf.Clamp(camPitch, -clampAngle, clampAngle); aim.transform.eulerAngles = new Vector3(camPitch, camYaw, 0.0f); } // if you are decelerating you can't accelerate as well // otherwise you can flip the ship around and // decelerate + accelerate against the velocity vector // aka cheating? Vector3 getThrust() { float accelerateInput = input.ship.accelerate.ReadValue(); float decelerateInput = input.ship.decelerate.ReadValue(); // decelerates the ship against the direction it is moving if (decelerateInput > 0f) { // the velocity is already in world space so we just invert it return rigidBody.velocity.normalized * -1f * powerUnit.Deceleration * decelerateInput; } // thrust in the direction of the camera if (accelerateInput > 0f) // transform from the camera's forward vector into world space return aim.transform.TransformDirection(Vector3.forward) * powerUnit.Acceleration * accelerateInput; return Vector3.zero; } public void Respawn() { shipAudio.PlayNoise(); // IMPORTANT // stop the timer before we move the ship or anything // otherwise it might generate a checkpoint time // for the final sector timing.StopTimer(); thrust = new Vector3(0.0f, 0.0f, 0.0f); transform.position = initialPosition; rigidBody.transform.forward = initialForce; rigidBody.velocity = new Vector3(0f, 0f, 0f); rigidBody.angularVelocity = new Vector3(0f, 0f, 0f); rigidBody.transform.rotation = initialAim; aim.transform.rotation = initialAim; camPitch = initialAim.eulerAngles.x; camYaw = initialAim.eulerAngles.y; powerUnit.Reset(); } // stuff we have to do right as they come off the line void Begin() { timing.StartTimer(); } public void Damage(float amt, string src) { Debug.Log($"Damage {amt} from {src}"); shipAudio.PlayNoise(); // convert dmg to % to hide internals float dmg = (amt / powerUnit.ShieldMax) * 100f; timing.Message($"{dmg:0} Damage"); bool destroyed = powerUnit.ShieldReduce(amt); if (destroyed) { Debug.Log($"Ddddestroyed by {src}"); Respawn(); } } public void OnTrackLimits() { timing.Message("Track Limits"); Respawn(); } void Deploy(Output output) { bool deployed = powerUnit.Deploy(output); if (deployed) shipAudio.PlayNoise(); } // I prefer doing it this way than registering the callbacks etc // the control of the actions is more granular // plus the callbacks don't have to be public, bonus void SetupActions() { if (input == null) input = new SpaceRaceInput(); input.ship.Enable(); // we have to update strafe the whole time it's being pressed // including setting it back to zero when released input.ship.strafe.started += OnStrafe; input.ship.strafe.performed += OnStrafe; input.ship.strafe.canceled += OnStrafe; // these we only care about the initial press input.ship.respawn.started += OnRespawn; input.ship.quit.started += OnQuit; input.ship.powerUnit1.started += OnPowerUnit1; input.ship.powerUnit2.started += OnPowerUnit2; input.ship.powerUnit3.started += OnPowerUnit3; input.ship.powerUnit4.started += OnPowerUnit4; } void OnDestroy() { input.ship.strafe.started -= OnStrafe; input.ship.strafe.performed -= OnStrafe; input.ship.strafe.canceled -= OnStrafe; // these we only care about the initial press input.ship.respawn.started -= OnRespawn; input.ship.quit.started -= OnQuit; input.ship.powerUnit1.started -= OnPowerUnit1; input.ship.powerUnit2.started -= OnPowerUnit2; input.ship.powerUnit3.started -= OnPowerUnit3; input.ship.powerUnit4.started -= OnPowerUnit4; } void OnStrafe(InputAction.CallbackContext ctx) { Vector2 str = ctx.ReadValue(); strafe = new Vector3(str.x, str.y, 0f) * powerUnit.Strafing; } void OnQuit(InputAction.CallbackContext ctx) => Application.Quit(); void OnRespawn(InputAction.CallbackContext ctx) => Respawn(); void OnPowerUnit1(InputAction.CallbackContext ctx) => Deploy(Output.Acceleration); void OnPowerUnit2(InputAction.CallbackContext ctx) => Deploy(Output.Deceleration); void OnPowerUnit3(InputAction.CallbackContext ctx) => Deploy(Output.Strafing); void OnPowerUnit4(InputAction.CallbackContext ctx) => Deploy(Output.Shield); void OnBoostCharge() { // shipAudio.PlayNoise(); // do some ui fx whatever } void OnDrawGizmosSelected() { if (Application.isPlaying == false) return; Gizmos.color = Color.red; Vector3 right = aim.transform.TransformDirection(Vector3.right) * 10; Gizmos.DrawRay(transform.position, right); Gizmos.color = Color.green; Vector3 direction = aim.transform.TransformDirection(Vector3.up) * 10; Gizmos.DrawRay(transform.position, direction); Gizmos.color = Color.blue; Vector3 forward = aim.transform.TransformDirection(Vector3.forward) * 10; Gizmos.DrawRay(transform.position, forward); Gizmos.color = Color.yellow; Vector3 velocity = rigidBody.velocity; Gizmos.DrawRay(transform.position, velocity); Gizmos.color = Color.cyan; Gizmos.DrawRay(transform.position, thrust); } }