rotate toward cursor working (controller untested)

This commit is contained in:
Rowan 2025-05-03 09:13:19 -05:00
parent c9cc6173f0
commit 90dc606009
6 changed files with 95 additions and 89 deletions

View file

@ -1,39 +0,0 @@
import { Camera3D, float64, Vector3 } from 'godot'
export class CameraCache extends Object {
private _camera: Camera3D
get camera() { return this._camera }
private _angle: float64
get angle() {
return this._camera.rotation.y
}
private _dirty: boolean = true
constructor(initial_camera: Camera3D) {
super()
this._camera = initial_camera
this._angle = this._camera.rotation.y
}
update_camera(camera: Camera3D) {
this._camera = camera
this._dirty = true
}
recalculate() {
if (this._dirty) {
this._angle = this._camera.rotation.y
this._dirty = false
}
}
transform_vec(vec: Vector3): Vector3 {
return vec.rotated(Vector3.UP, this._angle)
}
}

View file

@ -1,22 +0,0 @@
import { Area3D, Camera3D, NodePath, PhysicsBody3D, Variant } from 'godot'
import { export_ } from 'godot.annotations'
import Player from './player'
import MessageBus from './message_bus'
export default class CameraTrigger extends Area3D {
@export_(Variant.Type.TYPE_NODE_PATH)
camera!: NodePath
_camera!: Camera3D
_ready(): void {
this._camera = this.get_node(this.camera) as Camera3D
}
_on_body_entered(body: PhysicsBody3D) {
if (body instanceof Player) {
this._camera?.make_current()
console.log(MessageBus.instance.active_camera_changed)
MessageBus.instance.active_camera_changed.emit(this._camera)
}
}
}

15
src/main_camera.ts Normal file
View file

@ -0,0 +1,15 @@
import { Camera3D } from 'godot'
import MessageBus from './message_bus'
export default class MainCamera extends Camera3D {
private static _instance: MainCamera
static get instance() {
return MainCamera._instance
}
_ready(): void {
MainCamera._instance = this
MessageBus.instance.active_camera_changed.emit(this)
}
}

View file

@ -2,16 +2,16 @@ import { Camera3D, Node, Signal1 } from 'godot'
import { signal } from 'godot.annotations' import { signal } from 'godot.annotations'
export default class MessageBus extends Node { export default class MessageBus extends Node {
private static _inst: MessageBus private static _instance: MessageBus
static get instance() { static get instance() {
return MessageBus._inst return MessageBus._instance
} }
@signal() @signal()
active_camera_changed!: Signal1<Camera3D> active_camera_changed!: Signal1<Camera3D>
_ready(): void { _ready(): void {
MessageBus._inst = this MessageBus._instance = this
} }
} }

View file

@ -1,4 +1,4 @@
import { Callable, Callable0, Callable1, Camera3D, CharacterBody3D, Color, float64, NodePath, ProjectSettings, Variant, Vector2, Vector3 } from 'godot' import { AnyCallable, AnySignal, Callable, Callable0, Callable1, Camera3D, CharacterBody3D, Color, float64, NodePath, ProjectSettings, Signal, Signal0, Signal1, Variant, Vector2, Vector3 } from 'godot'
import { export_, help, onready } from 'godot.annotations' import { export_, help, onready } from 'godot.annotations'
import PlayerInput from './player_input' import PlayerInput from './player_input'
import DebugDraw from './debug_draw' import DebugDraw from './debug_draw'
@ -6,33 +6,34 @@ import Interactor from './interactor'
import PlayerAnimation, { PlayerAnimationName } from './player_animation' import PlayerAnimation, { PlayerAnimationName } from './player_animation'
export default class Player extends CharacterBody3D { export default class Player extends CharacterBody3D {
gravity = ProjectSettings.get_setting('physics/3d/default_gravity') readonly gravity = ProjectSettings.get_setting('physics/3d/default_gravity')
@export_(Variant.Type.TYPE_NODE_PATH)
camera!: NodePath
private _camera!: Camera3D
@onready("Input") @onready("Input")
player_input!: PlayerInput readonly player_input!: PlayerInput
@onready("AnimationTree") @onready("AnimationTree")
player_animation!: PlayerAnimation readonly player_animation!: PlayerAnimation
@onready("Interactor") @onready("Interactor")
interactor!: Interactor readonly interactor!: Interactor
@help('Forward walk speed in units per second') @help('Forward walk speed in units per second')
@export_(Variant.Type.TYPE_FLOAT) @export_(Variant.Type.TYPE_FLOAT)
walk_speed = 3 readonly walk_speed = 3
@help('Run speed in units per second') @help('Run speed in units per second')
@export_(Variant.Type.TYPE_FLOAT) @export_(Variant.Type.TYPE_FLOAT)
run_speed = 6 readonly run_speed = 6
@help('Turn speed in rotations per second') @help('Turn speed in rotations per second')
@export_(Variant.Type.TYPE_FLOAT) @export_(Variant.Type.TYPE_FLOAT)
turn_speed = 1 readonly turn_speed = 1
_rotation_speed = 2 * Math.PI private _rotation_speed = 2 * Math.PI
@help('Aim turn speed in rotations per second')
@export_(Variant.Type.TYPE_FLOAT)
readonly aim_turn_speed = 2
private _aim_rotation_speed = 2 * Math.PI * 2
is_moving() { is_moving() {
return !this.velocity.is_zero_approx() return !this.velocity.is_zero_approx()
@ -53,15 +54,15 @@ export default class Player extends CharacterBody3D {
private _can_act: boolean = true private _can_act: boolean = true
private _wants_to_fire: boolean = false private _wants_to_fire: boolean = false
// TODO: abstract this horrible shit away
private _try_interact_callable!: Callable0 private _try_interact_callable!: Callable0
private _queue_fire_callable!: Callable0 private _queue_fire_callable!: Callable0
private _disable_action_callable!: Callable1<string> private _disable_action_callable!: Callable1<string>
private _enable_action_callable!: Callable1<string> private _enable_action_callable!: Callable1<string>
_ready(): void { _ready(): void {
this._camera = this.get_node(this.camera) as Camera3D
this._rotation_speed = this.turn_speed * 2 * Math.PI this._rotation_speed = this.turn_speed * 2 * Math.PI
this._aim_rotation_speed = this.aim_turn_speed * 2 * Math.PI
this._try_interact_callable = Callable.create( this._try_interact_callable = Callable.create(
this, this,
@ -167,8 +168,14 @@ export default class Player extends CharacterBody3D {
this.move_and_slide() this.move_and_slide()
} }
private _aim_at_direction() { // TODO: combine this with _rotate_toward as a general
this._rotate_toward // rotate_over_time_toward method
// right now, both rotate instantly
private _aim_toward(delta: float64) {
// TODO: this performs a costly sqrt operation. necessary?
const look = this.player_input.look_direction.project(this.global_position)
look.y = this.global_position.y
this.look_at(look, Vector3.UP, true)
} }
_physics_process(delta: float64): void { _physics_process(delta: float64): void {
@ -180,7 +187,7 @@ export default class Player extends CharacterBody3D {
if (this.player_input.is_aiming) { if (this.player_input.is_aiming) {
this.velocity = Vector3.ZERO this.velocity = Vector3.ZERO
this._aim_at_direction() this._aim_toward(delta)
return return
} }

View file

@ -1,6 +1,8 @@
import { float64, Input, InputEvent, InputEventJoypadButton, InputEventJoypadMotion, InputEventKey, InputEventMouse, InputEventMouseMotion, int32, Node3D, Signal0, Signal1, Variant, Vector2 } from 'godot' import { Callable, Callable1, Camera3D, float64, Input, InputEvent, InputEventJoypadButton, InputEventJoypadMotion, InputEventKey, InputEventMouse, InputEventMouseMotion, int32, Node3D, Signal0, Signal1, Variant, Vector2, Vector3 } from 'godot'
import { export_, signal } from 'godot.annotations' import { export_, signal } from 'godot.annotations'
import InputBuffer from './input_buffer' import InputBuffer from './input_buffer'
import MainCamera from './main_camera'
import MessageBus from './message_bus'
export const PlayerMovement = Object.freeze({ export const PlayerMovement = Object.freeze({
MoveLeft: 'move_left', MoveLeft: 'move_left',
@ -19,13 +21,37 @@ export const PlayerLook = Object.freeze({
class LookDirection { class LookDirection {
static Empty = new LookDirection(Vector2.ZERO, false) static Empty = new LookDirection(Vector2.ZERO, false)
readonly value: Vector2 _value: Vector2
readonly needs_projection: boolean needs_projection: boolean
camera?: Camera3D
get value() {
return this._value
}
set value(val: Vector2) {
this._value = val
}
constructor(value: Vector2, needs_projection: boolean) { constructor(value: Vector2, needs_projection: boolean) {
this.value = value this._value = value
this.needs_projection = needs_projection this.needs_projection = needs_projection
} }
accumulate(delta: Vector2) {
this.value = Vector2.ADD(this._value, delta)
}
project(to_position: Vector3): Vector3 {
if (this.camera) {
// TODO: this performance a sqrt operation,
// check if this is necessary
const distance = this.camera.global_position.distance_to(to_position)
return this.camera?.project_position(this._value, distance) ?? Vector3.ZERO
} else {
return Vector3.ZERO
}
}
} }
export const PlayerAction = Object.freeze({ export const PlayerAction = Object.freeze({
@ -112,6 +138,8 @@ export default class PlayerInput extends Node3D {
return this._changed_since_last_frame return this._changed_since_last_frame
} }
private _camera!: Camera3D
private _running: boolean = false private _running: boolean = false
private _interacting: boolean = false private _interacting: boolean = false
private _aiming: boolean = false private _aiming: boolean = false
@ -181,7 +209,16 @@ export default class PlayerInput extends Node3D {
return n * n return n * n
} }
private _await_camera_callable!: Callable1<Camera3D>
_ready(): void { _ready(): void {
this._camera = MainCamera.instance
this._await_camera_callable = Callable.create(this, this._await_camera)
MessageBus.instance.active_camera_changed.connect(this._await_camera_callable)
this._look_direction.camera = this._camera
this._min_range_sqr = this._sqr(this.min_range) this._min_range_sqr = this._sqr(this.min_range)
this._max_range_sqr = this._sqr(this.max_range) this._max_range_sqr = this._sqr(this.max_range)
@ -189,6 +226,12 @@ export default class PlayerInput extends Node3D {
this._input_buffer = new InputBuffer(this.input_buffer_size) this._input_buffer = new InputBuffer(this.input_buffer_size)
} }
private _await_camera(camera: Camera3D) {
console.log('got camera', camera)
this._camera = camera
this._look_direction.camera = camera
}
_process(_delta: float64): void { _process(_delta: float64): void {
const next_movement = Input.get_vector( const next_movement = Input.get_vector(
PlayerMovement.MoveLeft, PlayerMovement.MoveLeft,
@ -207,11 +250,13 @@ export default class PlayerInput extends Node3D {
PlayerLook.LookUp, PlayerLook.LookUp,
PlayerLook.LookDown PlayerLook.LookDown
) )
this._look_direction = new LookDirection(dir, false)
break this._look_direction.value = dir
this._look_direction.needs_projection = false
case Device.KeyboardMouse: case Device.KeyboardMouse:
dir = this.get_viewport().get_mouse_position() dir = this.get_viewport().get_mouse_position()
this._look_direction = new LookDirection(dir, true) this._look_direction.value = dir
this._look_direction.needs_projection = true
break break
} }