aiming
This commit is contained in:
+19
-3
@@ -10,12 +10,13 @@ extends FloatingBody
|
||||
@export var loadout: Dictionary[StringName, WeaponDef] = {} # WeaponSlot Name -> WeaponDef
|
||||
@export var weapon_groups: Dictionary[int, Array] = {} # group id -> slot names; group 0 is auto-built (all slots), editor value ignored
|
||||
|
||||
var desired_throttle := 0.0 # move_toward target for actual throttle
|
||||
var steer_input := 0.0
|
||||
var throttle := 0.0 # current throttle state of ship
|
||||
var desired_throttle := 0.0 # written by controller, move_toward target for actual throttle
|
||||
var steer_input := 0.0 # written by controllers
|
||||
var throttle := 0.0 # current throttle state of ship
|
||||
var _slots: Array[WeaponSlot] = []
|
||||
var _slots_by_name: Dictionary[StringName, WeaponSlot] = {}
|
||||
var fire_intents: Dictionary[int, bool] = {} # written by controllers, like steer_input
|
||||
var aim_point = null # Vector3 or null — written by controllers, like steer_input
|
||||
|
||||
func _ready() -> void:
|
||||
super()
|
||||
@@ -38,8 +39,11 @@ func _ready() -> void:
|
||||
func _physics_process(delta: float) -> void:
|
||||
super(delta)
|
||||
|
||||
# Propulsion
|
||||
throttle = move_toward(throttle, desired_throttle, throttle_response * delta)
|
||||
|
||||
# Weapons
|
||||
_process_aim(delta)
|
||||
_process_fire()
|
||||
|
||||
if submerged_ratio <= 0.0:
|
||||
@@ -74,3 +78,15 @@ func _process_fire() -> void:
|
||||
var slot: WeaponSlot = _slots_by_name.get(slot_name)
|
||||
if slot != null and slot.weapon != null:
|
||||
slot.weapon.try_fire()
|
||||
|
||||
func _process_aim(delta: float) -> void:
|
||||
for slot in _slots:
|
||||
if slot.weapon != null:
|
||||
slot.weapon.aim_at(aim_point, delta)
|
||||
|
||||
func aim_ray_exclusions() -> Array[RID]:
|
||||
var rids: Array[RID] = [get_rid()]
|
||||
for slot in _slots:
|
||||
if slot.weapon != null and slot.weapon.hitbox != null:
|
||||
rids.append(slot.weapon.hitbox.get_rid())
|
||||
return rids
|
||||
|
||||
@@ -7,6 +7,7 @@ extends Node
|
||||
current_controlled.desired_throttle = 0.0
|
||||
current_controlled.steer_input = 0.0
|
||||
current_controlled.fire_intents.clear()
|
||||
current_controlled.aim_point = null
|
||||
current_controlled = value
|
||||
if is_node_ready():
|
||||
if current_controlled != null:
|
||||
@@ -32,6 +33,9 @@ extends Node
|
||||
_possess()
|
||||
|
||||
@export var throttle_ramp := 0.5
|
||||
@export var aim_ray_length := 2000.0
|
||||
@export_flags_3d_physics var aim_collision_mask := 0b1100111 # terrain, player, enemies, water, subsystems
|
||||
@export var aim_marker: Node3D
|
||||
|
||||
var desired_throttle := 0.0
|
||||
|
||||
@@ -41,6 +45,18 @@ func _ready() -> void:
|
||||
desired_throttle = current_controlled.throttle
|
||||
_possess()
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if current_controlled == null:
|
||||
if aim_marker != null:
|
||||
aim_marker.visible = false
|
||||
return
|
||||
current_controlled.aim_point = _compute_aim_point()
|
||||
if aim_marker != null:
|
||||
var p = current_controlled.aim_point
|
||||
aim_marker.visible = p != null
|
||||
if p != null:
|
||||
aim_marker.global_position = p
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if current_controlled == null:
|
||||
return
|
||||
@@ -78,3 +94,28 @@ func _possess_next() -> void:
|
||||
return
|
||||
var idx := boats.find(current_controlled)
|
||||
current_controlled = boats[(idx + 1) % boats.size()]
|
||||
|
||||
func _compute_aim_point():
|
||||
if camera_rig == null or camera_rig.cam == null:
|
||||
return null
|
||||
var cam := camera_rig.cam
|
||||
var mouse := get_viewport().get_mouse_position()
|
||||
var from := cam.project_ray_origin(mouse)
|
||||
var dir := cam.project_ray_normal(mouse)
|
||||
|
||||
var query := PhysicsRayQueryParameters3D.create(
|
||||
from, from + dir * aim_ray_length,
|
||||
aim_collision_mask, current_controlled.aim_ray_exclusions()
|
||||
)
|
||||
var hit := current_controlled.get_world_3d().direct_space_state.intersect_ray(query)
|
||||
if hit.is_empty():
|
||||
return from + dir * aim_ray_length # above the horizon
|
||||
|
||||
if hit.collider.is_in_group(&"water"):
|
||||
# Flat-plane hit → refine to the actual wave surface.
|
||||
var ocean := get_tree().get_first_node_in_group(&"ocean") as Ocean
|
||||
if ocean != null:
|
||||
var refined = Plane(Vector3.UP, ocean.get_wave_height(hit.position)).intersects_ray(from, dir)
|
||||
if refined != null:
|
||||
return refined
|
||||
return hit.position
|
||||
|
||||
@@ -13,3 +13,7 @@ enum Type {BALLISTIC, ENERGY, MISSILE, TORPEDO}
|
||||
@export var muzzle_speed_scale := 1.0 # multiplier over ammo.muzzle_speed (barrel length)
|
||||
@export var size := Size.MEDIUM
|
||||
@export var type := Type.BALLISTIC
|
||||
@export var traverse_speed_deg := 30.0 # yaw slew rate, degrees/second
|
||||
@export var elevation_speed_deg := 20.0 # pitch slew rate, degrees/second
|
||||
@export var elevation_min_deg := -10.0 # how far the barrel can depress
|
||||
@export var elevation_max_deg := 60.0 # how far it can elevate
|
||||
|
||||
@@ -8,6 +8,9 @@ var shooter: PhysicsBody3D # Resolved in _ready(): nearest ancestor body
|
||||
var _cooldown := 0.0
|
||||
|
||||
@onready var _muzzle: Marker3D = %Muzzle
|
||||
@onready var _yaw: Node3D = get_node_or_null("%Yaw")
|
||||
@onready var _pitch: Node3D = get_node_or_null("%Pitch")
|
||||
@onready var hitbox: PhysicsBody3D = get_node_or_null("%Hitbox")
|
||||
|
||||
func _ready() -> void:
|
||||
# Fallback: nearest ancestor body is the shooter (hand-placed weapons).
|
||||
@@ -27,5 +30,39 @@ func try_fire() -> bool:
|
||||
get_tree().current_scene.add_child(p)
|
||||
if shooter != null:
|
||||
p.add_collision_exception_with(shooter)
|
||||
if hitbox != null:
|
||||
p.add_collision_exception_with(hitbox)
|
||||
p.launch(def.ammo, _muzzle.global_transform, def.muzzle_speed_scale)
|
||||
return true
|
||||
|
||||
func aim_at(point, delta: float) -> void:
|
||||
if _yaw == null or def == null:
|
||||
return
|
||||
|
||||
var yaw_step := deg_to_rad(def.traverse_speed_deg) * delta
|
||||
var pitch_step := deg_to_rad(def.elevation_speed_deg) * delta
|
||||
|
||||
if point == null:
|
||||
# Nobody aiming: slew back to the stowed position.
|
||||
_yaw.rotation.y = rotate_toward(_yaw.rotation.y, 0.0, yaw_step)
|
||||
if _pitch != null:
|
||||
_pitch.rotation.x = rotate_toward(_pitch.rotation.x, 0.0, pitch_step)
|
||||
return
|
||||
|
||||
# Yaw: desired angle lives in the space of the node we rotate *within*,
|
||||
# i.e. the Yaw node's parent.
|
||||
var local: Vector3 = _yaw.get_parent_node_3d().to_local(point)
|
||||
var desired_yaw := atan2(-local.x, -local.z)
|
||||
_yaw.rotation.y = rotate_toward(_yaw.rotation.y, desired_yaw, yaw_step)
|
||||
|
||||
if _pitch == null:
|
||||
return
|
||||
|
||||
# Pitch: same idea, in the Pitch node's parent space (the freshly-yawed Yaw node).
|
||||
var lp: Vector3 = _pitch.get_parent_node_3d().to_local(point)
|
||||
var desired_pitch := clampf(
|
||||
atan2(lp.y, Vector2(lp.x, lp.z).length()),
|
||||
deg_to_rad(def.elevation_min_deg),
|
||||
deg_to_rad(def.elevation_max_deg)
|
||||
)
|
||||
_pitch.rotation.x = rotate_toward(_pitch.rotation.x, desired_pitch, pitch_step)
|
||||
|
||||
Reference in New Issue
Block a user