first commit
This commit is contained in:
175
scripts/player.gd
Normal file
175
scripts/player.gd
Normal file
@@ -0,0 +1,175 @@
|
||||
extends Node3D
|
||||
## First-Person-Spieler. Bewegt sich in Zugkoordinaten (s laengs, x quer),
|
||||
## damit der Innenraum auch bei voller Fahrt stabil begehbar bleibt.
|
||||
|
||||
const Mat = preload("res://scripts/mat.gd")
|
||||
const Deco = preload("res://scripts/deco.gd")
|
||||
const Car = preload("res://scripts/car.gd")
|
||||
|
||||
signal interact_pressed(hotspot: Dictionary)
|
||||
|
||||
enum Mode { WALK, DRIVE }
|
||||
|
||||
const RADIUS := 0.30
|
||||
const HEIGHT := 1.75
|
||||
const EYE := 1.62
|
||||
const WALK_SPEED := 2.6
|
||||
const RUN_SPEED := 4.6
|
||||
const ACCEL := 14.0
|
||||
const REACH := 2.0
|
||||
const MOUSE_SENS := 0.0022
|
||||
|
||||
var s := 0.0
|
||||
var x := 0.0
|
||||
var yaw := 0.0
|
||||
var pitch := 0.0
|
||||
var mode := Mode.WALK
|
||||
var carrying := 0 ## getragene Getränke
|
||||
var hotspot: Dictionary = {} ## aktuell anvisierter Interaktionspunkt
|
||||
var input_locked := false ## wenn ein Menue offen ist
|
||||
|
||||
var camera: Camera3D
|
||||
var train
|
||||
|
||||
var _vel := Vector2.ZERO ## (quer, laengs)
|
||||
|
||||
func setup(train_node) -> void:
|
||||
train = train_node
|
||||
camera = Camera3D.new()
|
||||
camera.fov = 75.0
|
||||
camera.near = 0.05
|
||||
camera.far = 900.0
|
||||
add_child(camera)
|
||||
snap_to_cab()
|
||||
|
||||
func snap_to_cab() -> void:
|
||||
if train.cars.is_empty():
|
||||
return
|
||||
var loco = train.cars[0]
|
||||
s = loco.center_s + loco.inner_z1 - 2.4
|
||||
x = 0.0
|
||||
yaw = 0.0
|
||||
pitch = 0.0
|
||||
# Falls dort etwas steht, nach hinten ausweichen.
|
||||
var span: Vector2 = train.walk_range()
|
||||
var tries := 0
|
||||
while _blocked(x, s) and tries < 40:
|
||||
s = maxf(s - 0.25, span.x)
|
||||
tries += 1
|
||||
|
||||
## Aktuelles Lauftempo, für die Schrittgeräusche.
|
||||
func move_speed() -> float:
|
||||
return _vel.length()
|
||||
|
||||
func capture_mouse(on: bool) -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if on else Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
yaw -= event.relative.x * MOUSE_SENS
|
||||
pitch = clampf(pitch - event.relative.y * MOUSE_SENS, -1.35, 1.35)
|
||||
elif event is InputEventKey and event.pressed and not event.echo:
|
||||
if event.keycode == KEY_E and not input_locked:
|
||||
if not hotspot.is_empty():
|
||||
interact_pressed.emit(hotspot)
|
||||
|
||||
# --- Bewegung -------------------------------------------------------------
|
||||
|
||||
func forward_dir() -> Vector2:
|
||||
## (quer, laengs) im Zugsystem
|
||||
return Vector2(sin(yaw), cos(yaw))
|
||||
|
||||
## Bildschirm-rechts im Zugsystem. Godots Kamerabasis hat +X nach links,
|
||||
## deshalb das umgekehrte Vorzeichen zu forward_dir().
|
||||
func right_dir() -> Vector2:
|
||||
return Vector2(-cos(yaw), sin(yaw))
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if train == null or train.cars.is_empty():
|
||||
return
|
||||
if mode == Mode.WALK and not input_locked:
|
||||
_walk(delta)
|
||||
else:
|
||||
_vel = Vector2.ZERO
|
||||
_update_transform()
|
||||
_pick_hotspot()
|
||||
|
||||
func _walk(delta: float) -> void:
|
||||
var move := Vector2.ZERO
|
||||
if Input.is_key_pressed(KEY_W):
|
||||
move += forward_dir()
|
||||
if Input.is_key_pressed(KEY_S):
|
||||
move -= forward_dir()
|
||||
if Input.is_key_pressed(KEY_D):
|
||||
move += right_dir()
|
||||
if Input.is_key_pressed(KEY_A):
|
||||
move -= right_dir()
|
||||
if move.length() > 0.001:
|
||||
move = move.normalized()
|
||||
var target: float = RUN_SPEED if Input.is_key_pressed(KEY_SHIFT) else WALK_SPEED
|
||||
_vel = _vel.move_toward(move * target, ACCEL * delta)
|
||||
|
||||
var step := _vel * delta
|
||||
# Achsenweise schieben, damit man an Hindernissen entlanggleitet.
|
||||
if not _blocked(x + step.x, s):
|
||||
x += step.x
|
||||
if not _blocked(x, s + step.y):
|
||||
s += step.y
|
||||
|
||||
var span: Vector2 = train.walk_range()
|
||||
s = clampf(s, span.x, span.y)
|
||||
|
||||
func _blocked(nx: float, ns: float) -> bool:
|
||||
var loc: Dictionary = train.locate(ns)
|
||||
var car = train.cars[int(loc["index"])]
|
||||
var lz: float = float(loc["local_z"])
|
||||
var half: float = Car.GANGWAY_HALF if bool(loc["gangway"]) else Car.INNER_HALF
|
||||
if absf(nx) > half - RADIUS:
|
||||
return true
|
||||
var y0: float = Car.FLOOR_Y + 0.05
|
||||
var y1: float = Car.FLOOR_Y + HEIGHT
|
||||
for b in car.blockers:
|
||||
var box: AABB = b
|
||||
if box.position.y + box.size.y <= y0 or box.position.y >= y1:
|
||||
continue
|
||||
if nx + RADIUS <= box.position.x or nx - RADIUS >= box.position.x + box.size.x:
|
||||
continue
|
||||
if lz + RADIUS <= box.position.z or lz - RADIUS >= box.position.z + box.size.z:
|
||||
continue
|
||||
return true
|
||||
return false
|
||||
|
||||
func _update_transform() -> void:
|
||||
var loc: Dictionary = train.locate(s)
|
||||
var xf: Transform3D = train.car_transform(int(loc["index"]))
|
||||
var lz: float = float(loc["local_z"])
|
||||
global_position = xf * Vector3(x, Car.FLOOR_Y, lz)
|
||||
var look := Vector3(sin(yaw) * cos(pitch), sin(pitch), cos(yaw) * cos(pitch))
|
||||
global_basis = xf.basis * Basis.looking_at(look, Vector3.UP)
|
||||
camera.position = Vector3(0, EYE, 0)
|
||||
|
||||
# --- Interaktion ----------------------------------------------------------
|
||||
|
||||
func _pick_hotspot() -> void:
|
||||
hotspot = {}
|
||||
if input_locked:
|
||||
return
|
||||
var best_score := 0.35
|
||||
var fwd := forward_dir()
|
||||
for ci in train.cars.size():
|
||||
var car = train.cars[ci]
|
||||
for h in car.hotspots:
|
||||
var p: Vector3 = h["pos"]
|
||||
var hs: float = car.center_s + p.z
|
||||
var d := Vector2(p.x - x, hs - s)
|
||||
var dist := d.length()
|
||||
if dist > REACH:
|
||||
continue
|
||||
if absf(p.y - (Car.FLOOR_Y + EYE)) > 1.7:
|
||||
continue
|
||||
var dir := d.normalized() if dist > 0.01 else fwd
|
||||
var score := fwd.dot(dir) * (1.0 - dist / (REACH * 2.0))
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
hotspot = {"car": ci, "kind": String(h["kind"]), "seat": int(h["seat"]),
|
||||
"pos": p, "dist": dist}
|
||||
Reference in New Issue
Block a user