// GDnD wiki example // Demonstrates: inertial vector movement (rotate + thrust + drag), firing along a heading, and asteroid collision // Related pages: [[overview-arcade-classics-as-learning-projects]], [[unity-input]], [[steering-behaviours]] using UnityEngine; // The first ship in the route with momentum. Thrust adds to a velocity vector // rather than setting position directly, so the ship drifts and turns are about // managing inertia. Pair with ScreenWrap2D so it wraps at the edges. public class ShipController : MonoBehaviour { [Header("Movement")] [SerializeField] private float rotationSpeed = 200f; // degrees per second [SerializeField] private float thrustPower = 12f; [SerializeField] private float maxSpeed = 9f; [Tooltip("Fraction of speed shed per second when not thrusting (gentle space friction).")] [SerializeField, Range(0f, 5f)] private float drag = 0.4f; [Header("Firing")] [SerializeField] private Bullet bulletPrefab; [SerializeField] private float bulletSpeed = 12f; [SerializeField] private float fireCooldown = 0.25f; [SerializeField] private LayerMask asteroidMask; [Header("Collision")] [SerializeField] private float collisionRadius = 0.4f; private AsteroidsGame game; private Vector2 velocity; private float cooldownTimer; private float invulnerableTimer; public void Bind(AsteroidsGame owner) { game = owner; } // Reset to a clean state at the centre after a death. public void Respawn(float invulnerableSeconds) { transform.position = Vector3.zero; transform.rotation = Quaternion.identity; velocity = Vector2.zero; invulnerableTimer = invulnerableSeconds; } private void Update() { Rotate(); Thrust(); Integrate(); TickFire(); TickCollision(); } private void Rotate() { float turn = Input.GetAxisRaw("Horizontal"); // A/D or left/right transform.Rotate(0f, 0f, -turn * rotationSpeed * Time.deltaTime); } private void Thrust() { // Only forward thrust (transform.up is the ship's nose). No reverse — classic feel. if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) { velocity += (Vector2)transform.up * (thrustPower * Time.deltaTime); } else { velocity *= Mathf.Clamp01(1f - drag * Time.deltaTime); } velocity = Vector2.ClampMagnitude(velocity, maxSpeed); } private void Integrate() { transform.position += (Vector3)(velocity * Time.deltaTime); } private void TickFire() { cooldownTimer -= Time.deltaTime; if (cooldownTimer > 0f || !Input.GetKey(KeyCode.Space) || bulletPrefab == null) { return; } cooldownTimer = fireCooldown; Bullet bullet = Instantiate(bulletPrefab, transform.position + transform.up * 0.5f, Quaternion.identity); bullet.Launch(transform.up, bulletSpeed, asteroidMask); } private void TickCollision() { if (invulnerableTimer > 0f) { invulnerableTimer -= Time.deltaTime; return; } if (Physics2D.OverlapCircle(transform.position, collisionRadius, asteroidMask) != null) { game?.OnShipHit(); } } }