using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class ShipControls : MonoBehaviour { public Camera aim; public Timing timing; 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; public PowerUnit powerUnit = new(); void Start() { Cursor.lockState = CursorLockMode.Locked; rigidBody = GetComponent(); // 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); // 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) { powerUnit.Tick(); } } 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; camPitch -= lookDelta.y; 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() { // 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 float Speed() { return rigidBody.velocity.magnitude; } public void Damage(float amt, string src) { Debug.Log($"Damage {amt} from {src}"); bool destroyed = powerUnit.ShieldReduce(amt); if (destroyed) { Debug.Log($"Ddddestroyed by {src}"); Respawn(); } } void Deploy(Output output) { powerUnit.Deploy(output); } // 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 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 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); } }