commit 06339fbed3ea3d33e4a5cfe662ad8d9ba5d9130e Author: Zeke Abshire Date: Fri Apr 3 23:21:32 2026 -0500 feat: init project + basic ship controller diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f28239b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*] +charset = utf-8 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0af181c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..c6bbb7d --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..4a884a6 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6rk0cfu71wnd" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/main.tscn b/main.tscn new file mode 100644 index 0000000..276970d --- /dev/null +++ b/main.tscn @@ -0,0 +1,82 @@ +[gd_scene format=3 uid="uid://biwfc6l2lnu11"] + +[ext_resource type="Script" uid="uid://b83vmbx10mlqe" path="res://ship_controller.gd" id="1_0xm2m"] +[ext_resource type="PackedScene" uid="uid://iogkdi8dv0nv" path="res://projectile.tscn" id="2_1bvp3"] +[ext_resource type="Script" uid="uid://inqwtwfui2tr" path="res://ship_camera.gd" id="2_h2yge"] + +[sub_resource type="BoxMesh" id="BoxMesh_1bvp3"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_lquwl"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_h2yge"] + +[sub_resource type="Sky" id="Sky_1bvp3"] +sky_material = SubResource("ProceduralSkyMaterial_h2yge") + +[sub_resource type="Environment" id="Environment_0xm2m"] +background_mode = 2 +sky = SubResource("Sky_1bvp3") +ambient_light_source = 3 + +[node name="Main" type="Node3D" unique_id=1472247091] + +[node name="Ship" type="CharacterBody3D" parent="." unique_id=122234868 node_paths=PackedStringArray("ship_model", "muzzle")] +script = ExtResource("1_0xm2m") +turn_speed = 4.975 +projectile_scene = ExtResource("2_1bvp3") +ship_model = NodePath("ShipModel") +muzzle = NodePath("Muzzle") + +[node name="ShipModel" type="MeshInstance3D" parent="Ship" unique_id=352448396] +mesh = SubResource("BoxMesh_1bvp3") + +[node name="HoverRay" type="RayCast3D" parent="Ship" unique_id=1076533010] +target_position = Vector3(0, -5, 0) + +[node name="Muzzle" type="Marker3D" parent="Ship" unique_id=322799309] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.5) + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Ship" unique_id=567390614] +shape = SubResource("BoxShape3D_lquwl") + +[node name="CameraRig" type="Node3D" parent="." unique_id=920354244 node_paths=PackedStringArray("target")] +script = ExtResource("2_h2yge") +target = NodePath("../Ship") +follow_distance = 8.57 +position_smoothing = 18.56 +rotation_smoothing = 14.5 + +[node name="Camera3D" type="Camera3D" parent="CameraRig" unique_id=2079353001] + +[node name="CSGCombiner3D" type="CSGCombiner3D" parent="." unique_id=1690012445] + +[node name="CSGBox3D" type="CSGBox3D" parent="CSGCombiner3D" unique_id=2019756943] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12, 0.07247639, -21.964855) + +[node name="CSGBox3D2" type="CSGBox3D" parent="CSGCombiner3D" unique_id=842749163] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -24, 0.07247639, 17.169182) + +[node name="CSGBox3D3" type="CSGBox3D" parent="CSGCombiner3D" unique_id=2106821369] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -36, 6.410921, -17.84053) + +[node name="CSGBox3D4" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1236194522] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -48, 6.410921, 14.210682) + +[node name="CSGBox3D5" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1570750803] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -36, -5.170101, -25.111805) + +[node name="CSGBox3D6" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1205494666] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -51.54701, 1.1025181, 0) + +[node name="CSGBox3D7" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1295905165] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -60, 12.749367, -34.99634) + +[node name="CSGBox3D8" type="CSGBox3D" parent="CSGCombiner3D" unique_id=749127710] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -72, 12.749367, 0) + +[node name="CSGBox3D9" type="CSGBox3D" parent="CSGCombiner3D" unique_id=1499064177] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16.63602, 0) +size = Vector3(500, 1, 500) + +[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=1014540785] +environment = SubResource("Environment_0xm2m") diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..cbf3a18 --- /dev/null +++ b/project.godot @@ -0,0 +1,57 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Mercury" +run/main_scene="uid://biwfc6l2lnu11" +config/features=PackedStringArray("4.6", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +accelerate={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +brake={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +strafe_left={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +strafe_right={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +boost={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +shoot={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} + +[physics] + +3d/physics_engine="Jolt Physics" + +[rendering] + +rendering_device/driver.windows="d3d12" diff --git a/projectile.gd b/projectile.gd new file mode 100644 index 0000000..84d3963 --- /dev/null +++ b/projectile.gd @@ -0,0 +1,35 @@ +extends Area3D +class_name HovercraftProjectile +## Simple bullet that travels forward and destroys itself on hit or timeout. + +@export var speed: float = 80.0 +@export var lifetime: float = 3.0 +@export var damage: float = 10.0 + +var _direction: Vector3 = Vector3.FORWARD +var _age: float = 0.0 + + +func _ready() -> void: + # Use the node's forward direction if no explicit direction was set + if _direction == Vector3.FORWARD: + _direction = - global_transform.basis.z + + body_entered.connect(_on_body_entered) + + +func set_direction(dir: Vector3) -> void: + _direction = dir.normalized() + + +func _physics_process(delta: float) -> void: + global_position += _direction * speed * delta + _age += delta + if _age >= lifetime: + queue_free() + + +func _on_body_entered(body: Node3D) -> void: + if body.has_method("take_damage"): + body.take_damage(damage) + queue_free() \ No newline at end of file diff --git a/projectile.gd.uid b/projectile.gd.uid new file mode 100644 index 0000000..5099587 --- /dev/null +++ b/projectile.gd.uid @@ -0,0 +1 @@ +uid://cpqdkciafcu51 diff --git a/projectile.tscn b/projectile.tscn new file mode 100644 index 0000000..b22f633 --- /dev/null +++ b/projectile.tscn @@ -0,0 +1,16 @@ +[gd_scene format=3 uid="uid://iogkdi8dv0nv"] + +[ext_resource type="Script" uid="uid://cpqdkciafcu51" path="res://projectile.gd" id="1_a85iw"] + +[sub_resource type="SphereMesh" id="SphereMesh_akgne"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_a85iw"] + +[node name="Projectile" type="Area3D" unique_id=24893414] +script = ExtResource("1_a85iw") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=1868961436] +mesh = SubResource("SphereMesh_akgne") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1475750571] +shape = SubResource("SphereShape3D_a85iw") diff --git a/ship_camera.gd b/ship_camera.gd new file mode 100644 index 0000000..fe79f94 --- /dev/null +++ b/ship_camera.gd @@ -0,0 +1,46 @@ +extends Node3D +class_name ShipCamera +## Third-person camera for a freely flying ship. +## Stays behind and above the ship regardless of pitch/yaw. +## Attach as a sibling of the ship (child of the scene root), NOT as a child +## of the ship, so it can lag smoothly. + +@export var target: ShipController +@export var follow_distance: float = 12.0 +@export var follow_height: float = 3.0 ## offset along the ship's local UP +@export var look_ahead: float = 4.0 +@export var position_smoothing: float = 5.0 +@export var rotation_smoothing: float = 6.0 + +@onready var camera: Camera3D = $Camera3D + + +func _ready() -> void: + if not camera: + camera = Camera3D.new() + add_child(camera) + camera.current = true + + +func _physics_process(delta: float) -> void: + if not target: + return + + # "Behind" the ship in its local space (accounts for pitch + yaw) + var ship_back := target.global_transform.basis.z # +Z is behind in Godot + var ship_up := target.global_transform.basis.y + + var desired_pos := ( + target.global_position + + ship_back * follow_distance + + ship_up * follow_height + ) + + global_position = global_position.lerp(desired_pos, position_smoothing * delta) + + # Look at a point ahead of the ship + var ship_forward := -target.global_transform.basis.z + var look_target := target.global_position + ship_forward * look_ahead + + var desired_transform := global_transform.looking_at(look_target, Vector3.UP) + global_transform = global_transform.interpolate_with(desired_transform, rotation_smoothing * delta) \ No newline at end of file diff --git a/ship_camera.gd.uid b/ship_camera.gd.uid new file mode 100644 index 0000000..6cd0043 --- /dev/null +++ b/ship_camera.gd.uid @@ -0,0 +1 @@ +uid://inqwtwfui2tr diff --git a/ship_controller.gd b/ship_controller.gd new file mode 100644 index 0000000..6005e92 --- /dev/null +++ b/ship_controller.gd @@ -0,0 +1,262 @@ +extends CharacterBody3D +class_name ShipController +## FP4-style ship controller for Godot 4.6 +## +## Full 3D flight — the ship flies wherever the mouse aims. +## Mouse X = yaw, Mouse Y = pitch. W accelerates along the aim direction, +## S brakes, A/D strafe laterally. No gravity, no hover, no ground. +## The visual model applies cosmetic roll/pitch on top of the actual orientation. + +# ── Movement tuning ────────────────────────────────────────────────────────── +@export_group("Movement") +@export var max_speed: float = 30.0 +@export var acceleration: float = 40.0 +@export var brake_deceleration: float = 50.0 +@export var strafe_speed: float = 20.0 +@export var friction: float = 12.0 ## passive drag when no input + +@export_group("Boost") +@export var boost_multiplier: float = 1.8 +@export var boost_max: float = 100.0 +@export var boost_drain_rate: float = 30.0 +@export var boost_recharge_rate: float = 15.0 +@export var boost_min_to_activate: float = 10.0 + +# ── Cosmetic tilt ──────────────────────────────────────────────────────────── +@export_group("Visual Tilt") +@export var max_roll_deg: float = 25.0 ## extra bank on strafe / turning +@export var max_pitch_deg: float = 15.0 ## extra nose-down on accel +@export var tilt_lerp_speed: float = 6.0 + +# ── Mouse ──────────────────────────────────────────────────────────────────── +@export_group("Mouse") +@export var mouse_sensitivity: float = 0.003 +@export var pitch_limit_deg: float = 85.0 ## clamp so you can't fully invert + +# ── Combat ─────────────────────────────────────────────────────────────────── +@export_group("Combat") +@export var fire_rate: float = 10.0 +@export var projectile_scene: PackedScene + +# ── Node references ────────────────────────────────────────────────────────── +@export_group("Node References") +@export var ship_model: Node3D ## visual mesh that gets cosmetic tilt +@export var muzzle: Marker3D ## bullet spawn point + +# ── Internal state ─────────────────────────────────────────────────────────── +var _forward_speed: float = 0.0 +var _strafe_speed: float = 0.0 + +# Mouse-driven aim targets (body smoothly rotates toward these) +var _target_yaw: float = 0.0 +var _target_pitch: float = 0.0 +var _current_yaw: float = 0.0 +var _current_pitch: float = 0.0 + +var _boost_energy: float = 0.0 +var _is_boosting: bool = false +var _fire_cooldown: float = 0.0 + +# Cosmetic tilt state (applied to ship_model only) +var _visual_roll: float = 0.0 +var _visual_extra_pitch: float = 0.0 + + +func _ready() -> void: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + _boost_energy = boost_max + _current_yaw = rotation.y + _current_pitch = rotation.x + _target_yaw = _current_yaw + _target_pitch = _current_pitch + + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventMouseMotion: + _target_yaw -= event.relative.x * mouse_sensitivity + _target_pitch -= event.relative.y * mouse_sensitivity + # Clamp pitch so the ship can't go fully vertical / inverted + var limit := deg_to_rad(pitch_limit_deg) + _target_pitch = clampf(_target_pitch, -limit, limit) + + if event.is_action_pressed("ui_cancel"): + if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + else: + Input.mouse_mode = Input.MOUSE_MODE_CAPTURED + + +func _physics_process(delta: float) -> void: + _handle_turning(delta) + _handle_boost(delta) + _handle_movement(delta) + _apply_velocity(delta) + _handle_visual_tilt(delta) + _handle_shooting(delta) + + +# ── Turning (yaw + pitch) ─────────────────────────────────────────────────── +## Instead of lerp_angle (which breaks on large deltas and can snap backward +## near ±π), we use move_toward on the angular difference. This guarantees: +## 1. The ship always turns toward the target, never away from it. +## 2. The turn rate is capped at max_turn_rate rad/s so fast mouse flicks +## produce a fast but controlled rotation, not a teleport or reversal. + +@export var max_turn_rate: float = 12.0 ## radians/sec — max rotation speed + +func _handle_turning(delta: float) -> void: + # Yaw — target is unbounded (no wraparound bugs), difference is always correct + var yaw_diff := _target_yaw - _current_yaw + var yaw_step := minf(absf(yaw_diff), max_turn_rate * delta) + _current_yaw += signf(yaw_diff) * yaw_step + + # Pitch — same approach + var pitch_diff := _target_pitch - _current_pitch + var pitch_step := minf(absf(pitch_diff), max_turn_rate * delta) + _current_pitch += signf(pitch_diff) * pitch_step + + rotation.y = _current_yaw + rotation.x = _current_pitch + + +# ── Movement along the ship's facing direction ────────────────────────────── + +func _handle_movement(delta: float) -> void: + var forward_input: float = 0.0 + var strafe_input: float = 0.0 + + if Input.is_action_pressed("accelerate"): + forward_input = 1.0 + if Input.is_action_pressed("brake"): + forward_input -= 1.0 + if Input.is_action_pressed("strafe_left"): + strafe_input -= 1.0 + if Input.is_action_pressed("strafe_right"): + strafe_input += 1.0 + + var effective_max := max_speed * _boost_value() + var effective_accel := acceleration * _boost_value() + + # Forward / brake + if forward_input > 0.0: + _forward_speed = move_toward(_forward_speed, effective_max, effective_accel * delta) + elif forward_input < 0.0: + _forward_speed = move_toward(_forward_speed, 0.0, brake_deceleration * delta) + else: + _forward_speed = move_toward(_forward_speed, 0.0, friction * delta) + + # Strafe + if strafe_input != 0.0: + _strafe_speed = move_toward(_strafe_speed, strafe_speed * strafe_input, acceleration * delta) + else: + _strafe_speed = move_toward(_strafe_speed, 0.0, friction * delta) + + +# ── Boost ──────────────────────────────────────────────────────────────────── + +func _handle_boost(delta: float) -> void: + if Input.is_action_pressed("boost") and _boost_energy > boost_min_to_activate: + _is_boosting = true + _boost_energy = maxf(0.0, _boost_energy - boost_drain_rate * delta) + if _boost_energy <= 0.0: + _is_boosting = false + else: + _is_boosting = false + _boost_energy = minf(boost_max, _boost_energy + boost_recharge_rate * delta) + + +func _boost_value() -> float: + return boost_multiplier if _is_boosting else 1.0 + + +# ── Apply velocity in 3D ──────────────────────────────────────────────────── + +func _apply_velocity(delta: float) -> void: + # Forward is wherever the ship is pointing (includes pitch + yaw) + var forward_dir := -transform.basis.z + var right_dir := transform.basis.x + + var desired := forward_dir * _forward_speed + right_dir * _strafe_speed + + # Clamp total speed + var max_total := max_speed * _boost_value() + if desired.length() > max_total: + desired = desired.normalized() * max_total + + velocity = desired + move_and_slide() + + +# ── Cosmetic tilt on the visual model ──────────────────────────────────────── +## The CharacterBody3D itself rotates to face the aim direction (yaw + pitch). +## On top of that, the child ship_model gets extra roll from strafing/turning +## and extra pitch from acceleration — purely visual seasoning. + +func _handle_visual_tilt(delta: float) -> void: + if not ship_model: + return + + # ── Roll from turning + strafing ── + # yaw is unbounded now, so the raw difference is the true angular lag + var yaw_delta: float = _target_yaw - _current_yaw + + var strafe_input: float = 0.0 + if Input.is_action_pressed("strafe_left"): + strafe_input -= 1.0 + if Input.is_action_pressed("strafe_right"): + strafe_input += 1.0 + + var target_roll_deg: float = (-strafe_input * max_roll_deg * 0.6) + (yaw_delta * max_roll_deg * 2.0) + target_roll_deg = clampf(target_roll_deg, -max_roll_deg, max_roll_deg) + + # ── Extra pitch from acceleration ── + var forward_input: float = 0.0 + if Input.is_action_pressed("accelerate"): + forward_input = 1.0 + if Input.is_action_pressed("brake"): + forward_input -= 1.0 + + var target_extra_pitch_deg: float = - forward_input * max_pitch_deg + if _is_boosting: + target_extra_pitch_deg *= 1.4 + + # Smooth interpolation + _visual_roll = lerp(_visual_roll, deg_to_rad(target_roll_deg), tilt_lerp_speed * delta) + _visual_extra_pitch = lerp(_visual_extra_pitch, deg_to_rad(target_extra_pitch_deg), tilt_lerp_speed * delta) + + # Apply roll and extra pitch on top of the body's real orientation + ship_model.rotation = Vector3(_visual_extra_pitch, 0.0, _visual_roll) + + +# ── Shooting ───────────────────────────────────────────────────────────────── + +func _handle_shooting(delta: float) -> void: + _fire_cooldown = maxf(0.0, _fire_cooldown - delta) + + if Input.is_action_pressed("shoot") and _fire_cooldown <= 0.0 and projectile_scene: + _fire_cooldown = 1.0 / fire_rate + var bullet := projectile_scene.instantiate() as Node3D + get_tree().current_scene.add_child(bullet) + + if muzzle: + bullet.global_position = muzzle.global_position + bullet.global_transform.basis = muzzle.global_transform.basis + else: + bullet.global_position = global_position + (-transform.basis.z * 2.0) + bullet.global_transform.basis = global_transform.basis + + # Pass the full 3D forward direction to the bullet + if bullet.has_method("set_direction"): + bullet.set_direction(-global_transform.basis.z) + + +# ── Public API ─────────────────────────────────────────────────────────────── + +func get_boost_ratio() -> float: + return _boost_energy / boost_max + +func get_speed_ratio() -> float: + return _forward_speed / (max_speed * boost_multiplier) + +func is_boosting() -> bool: + return _is_boosting \ No newline at end of file diff --git a/ship_controller.gd.uid b/ship_controller.gd.uid new file mode 100644 index 0000000..9422db5 --- /dev/null +++ b/ship_controller.gd.uid @@ -0,0 +1 @@ +uid://b83vmbx10mlqe