reorganize everything

This commit is contained in:
2025-09-16 23:55:21 +10:00
parent c2ca2f57ac
commit af83010bd5
108 changed files with 150 additions and 150 deletions

View File

@@ -0,0 +1,27 @@
extends Node3D
@export var _velocity: Vector3 = Vector3.FORWARD
@export var _spawn_rate: float = 2
@export_group("References")
@export var _spawn_point: Node3D
@export var _projectile_scene: PackedScene
@export var _spawn_particles: GPUParticles3D
var _spawn_timer: float
func _ready() -> void:
_spawn_timer = _spawn_rate
_spawn_point.position.y = Projectile.HEIGHT
func _process(delta: float) -> void:
if _spawn_timer <= 0:
_spawn_timer = _spawn_rate
var projectile := _projectile_scene.instantiate() as Projectile
projectile.init(basis * _velocity, _spawn_point.global_position)
add_child(projectile)
_spawn_particles.emitting = true
_spawn_timer -= delta

View File

@@ -0,0 +1 @@
uid://dbyq5apxmiosn

View File

@@ -0,0 +1,93 @@
class_name Player
extends CharacterBody3D
static var instances: Array[Player]
@export var cursor_color: Color
@export var _input_mode: Inputer.Mode = Inputer.Mode.KB_MOUSE
@export var _device_index: int = 0
@export var _respawn_height: float = -5
@export_group("References")
@export var attack: PlayerAttacker
@export var stats: PlayerStats
@export var mover: PlayerMover
@export var aimer: PlayerAimer
@export var _cursor: PlayerCursor
var _respawn_point: Vector3
static func is_single_player() -> bool:
return instances.size() == 1
func _ready() -> void:
_respawn_point = global_position
instances.append(self)
func _process(_delta: float) -> void:
var aim_pos := global_position + aimer.aim_offset
Debugger.marker("aim", aim_pos + Vector3.UP)
Debugger.vector("aimv", Vector3(aim_pos.x, 0, aim_pos.z), aim_pos + Vector3.UP)
func _physics_process(delta: float) -> void:
_aiming()
var can_move := not attack.is_hitting()
velocity = mover.process_movement(velocity, delta, is_on_floor(), can_move)
move_and_slide()
if not attack.is_hitting() and aimer.aim_offset.length() > 0:
look_at(global_position + aimer.aim_offset, Vector3.UP, true)
_process_respawning()
_cursor.handle_cursor(self, delta)
func _unhandled_input(event: InputEvent) -> void:
var mode := Inputer.get_event_mode(event)
if (
not input_mode_is(mode)
or (
not Player.is_single_player()
and _input_mode == Inputer.Mode.CONTROLLER
and event.device != _device_index
)
):
return
aimer.handle_input(event, mode)
mover.handle_input(event, mode)
if event.is_action_pressed("attack"):
attack.attack()
func input_mode_is(mode: Inputer.Mode) -> bool:
return (
(Player.is_single_player() and mode == Inputer.mode)
or (not Player.is_single_player() and _input_mode == mode)
)
func _aiming() -> void:
if input_mode_is(Inputer.Mode.CONTROLLER):
aimer.aim_controller(mover.move_input)
if input_mode_is(Inputer.Mode.KB_MOUSE):
var mouse_pos := get_viewport().get_mouse_position()
if get_viewport().get_visible_rect().has_point(mouse_pos):
aimer.aim_mouse(mouse_pos, global_position, is_on_floor())
func _process_respawning() -> void:
if global_position.y < _respawn_height:
global_position = _respawn_point
velocity = Vector3.ZERO
reset_physics_interpolation()

View File

@@ -0,0 +1 @@
uid://bvvmaqn1fp6nq

View File

@@ -0,0 +1,85 @@
class_name PlayerAimer
extends Node
@export var _controller_aim_offset: float = 6
@export var _vertical_aim_aspect: float = 1.5
var aim_offset: Vector3
var aim_input: Vector2
var _floor_height: float
var _aim_left: float
var _aim_right: float
var _aim_up: float
var _aim_down: float
func handle_input(event: InputEvent, mode: Inputer.Mode) -> void:
if Player.is_single_player():
return
if mode == Inputer.Mode.CONTROLLER and event is InputEventJoypadMotion:
var motion_event := event as InputEventJoypadMotion
if motion_event.is_action("aim_left"):
_aim_left = motion_event.get_action_strength("aim_left")
if motion_event.is_action("aim_right"):
_aim_right = motion_event.get_action_strength("aim_right")
if motion_event.is_action("aim_up"):
_aim_up = motion_event.get_action_strength("aim_up")
if motion_event.is_action("aim_down"):
_aim_down = motion_event.get_action_strength("aim_down")
func aim_controller(move_input: Vector2) -> void:
if Player.is_single_player():
aim_input = Input.get_vector(
"aim_left",
"aim_right",
"aim_up",
"aim_down",
Settings.aiming_stick_deadzone
)
else:
aim_input = Inputer.get_vector_from_raw_strengths(
_aim_left, _aim_right, _aim_up, _aim_down, Settings.aiming_stick_deadzone
)
if aim_input.length() == 0 and move_input.length() == 0:
return
var input := (aim_input if aim_input.length() > 0 else move_input).normalized()
var aim_direction := Vector3(input.x, 0, input.y * _vertical_aim_aspect)
aim_offset = (
aim_direction.rotated(Vector3.UP, MainCamera.instance.rotation.y)
* _controller_aim_offset
)
func aim_mouse(
mouse_pos: Vector2, player_position: Vector3, is_on_floor: bool
) -> void:
var position_y := player_position.y
if is_on_floor:
_floor_height = player_position.y
player_position.y = _floor_height
var aim_position := _mouse_project(mouse_pos, _floor_height + Projectile.HEIGHT)
aim_offset = aim_position - player_position
aim_offset.y = 0
aim_position.y = position_y
func _mouse_project(mouse_pos: Vector2, height: float) -> Vector3:
var camera := MainCamera.instance
var from := camera.project_ray_origin(mouse_pos)
var direction := camera.project_ray_normal(mouse_pos)
var plane := Plane(Vector3.UP, height)
var intersection: Variant = plane.intersects_ray(from, direction)
if not intersection:
return Vector3.ZERO
return intersection as Vector3

View File

@@ -0,0 +1 @@
uid://c131c3hcbmu77

View File

@@ -0,0 +1,117 @@
extends AnimationTree
@export_group("References")
@export var _player: Player
@export var _attack: PlayerAttacker
@export var _bone_flipper: BoneFlipper
@export var _sfx_audio_player: AudioStreamPlayer3D
@export var _hurt_particles: GPUParticles3D
@export var _footsteps_player: FootstepsPlayer
@export_group("Audio")
@export var _hurt_sound: AudioStream
@export var _hit_sounds: Array[AudioStream]
@export var _swing_sounds: AudioStream
var _speed: float
var _has_input: bool
var _queue_hit_sound: bool
@onready var _sfx_audio_playback_polyphonic := (
_sfx_audio_player.get_stream_playback() as AudioStreamPlaybackPolyphonic
)
func _ready() -> void:
assert(_player, "_player missing!")
Music.track_started.connect(_on_music_track_started)
_set_bpm()
_attack.attacked.connect(_on_attack_attacked)
_attack.did_hit.connect(_on_attack_did_hit)
_player.stats.damaged.connect(_on_stats_damaged)
func _process(_delta: float) -> void:
_footsteps_player.can_play = _player.is_on_floor()
_bone_flipper.flip = _is_left()
var has_input_prev := _has_input
_speed = _player.velocity.length() / _player.mover.move_speed
_has_input = (
_player.mover.move_input.length() > 0 and not _player.attack.is_hitting()
)
var velocity_relative := _player.to_local(
_player.global_position + _player.velocity
)
var velocity_blend := (
Vector2(-velocity_relative.x, velocity_relative.z) / _player.mover.move_speed
)
if _is_left():
velocity_blend.x = -velocity_blend.x
Debugger.text("velocity_blend", velocity_blend, 2)
set(&"parameters/locomotion/run/blend_position", velocity_blend)
if has_input_prev != _has_input:
if _has_input:
_abort_oneshots()
else:
_run_to_idle()
if _queue_hit_sound:
_queue_hit_sound = false
for stream in _hit_sounds:
_play_sound(stream)
func _is_left() -> bool:
return _attack.side == PlayerAttacker.Side.LEFT
func _play_sound(stream: AudioStream) -> void:
_sfx_audio_playback_polyphonic.play_stream(stream)
func _set_bpm() -> void:
set(&"parameters/main_time_scale/scale", Music.bpm_factor)
func _run_to_idle() -> void:
set(
&"parameters/run->idle_oneshot/request",
AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE
)
func _abort_oneshots() -> void:
set(&"parameters/hit_oneshot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT)
set(
&"parameters/run->idle_oneshot/request",
AnimationNodeOneShot.ONE_SHOT_REQUEST_ABORT
)
func _on_attack_attacked() -> void:
set(&"parameters/hit_oneshot/request", AnimationNodeOneShot.ONE_SHOT_REQUEST_FIRE)
_play_sound(_swing_sounds)
func _on_attack_did_hit() -> void:
_queue_hit_sound = true
func _on_stats_damaged() -> void:
_play_sound(_hurt_sound)
_hurt_particles.restart()
_hurt_particles.emitting = true
for node in _hurt_particles.get_children():
if node is GPUParticles3D:
(node as GPUParticles3D).restart()
(node as GPUParticles3D).emitting = true
func _on_music_track_started() -> void:
_set_bpm()

View File

@@ -0,0 +1 @@
uid://dqxvdi3i2ejs

View File

@@ -0,0 +1,28 @@
extends MeshInstance3D
@export var _swoop_effect_time: float = 0.25
@export_group("References")
@export var _attack: PlayerAttacker
var _swoop_effect_timer: float
func _ready() -> void:
mesh.radius = _attack.attack_radius
mesh.height = _attack.attack_radius
_attack.attacked.connect(_on_attack_attacked)
func _process(delta: float) -> void:
if _swoop_effect_timer > 0:
_swoop_effect_timer -= delta
(material_override as StandardMaterial3D).albedo_color = Color(
1, 1, 1, _swoop_effect_timer / _swoop_effect_time
)
visible = _swoop_effect_timer > 0
func _on_attack_attacked() -> void:
_swoop_effect_timer = _swoop_effect_time

View File

@@ -0,0 +1 @@
uid://bxsmma3kjo381

View File

@@ -0,0 +1,161 @@
class_name PlayerAttacker
extends Area3D
signal attacked
signal did_hit
enum Side { RIGHT, LEFT }
@export_group("References")
@export var _attack_shape_node: CollisionShape3D
@export_group("Collision")
@export var _attack_max_angle: float = 2 * PI / 3
@export var attack_radius: float = 2
@export_group("Timers")
@export var _cooldown_time: float = 0.3
@export var _hit_window_time: float = 0.15
@export_group("Hits")
@export var _hit_projectile_speed: float = 35
@export var _direction_angles: Dictionary[float, float] = {0.0: 0.0, PI: PI / 2.0}
@export var _hit_stop_time_scale := 0.05
@export var _hit_stop_duration := 0.25
var side := Side.RIGHT
var _cooldown_timer: float
var _hit_window_timer: float
var _queue_hit_stop: bool
@onready var _attack_shape: CylinderShape3D = _attack_shape_node.shape as CylinderShape3D
func _ready() -> void:
Debugger.add_event("attacked")
attacked.connect(func() -> void: Debugger.event_emitted("attacked", []))
area_entered.connect(_on_area_entered)
position.y = Projectile.HEIGHT
_set_collision_size(attack_radius)
func _process(delta: float) -> void:
if _cooldown_timer > 0:
_cooldown_timer -= delta
if _hit_window_timer > 0:
_hit_window_timer -= delta
if _queue_hit_stop:
_queue_hit_stop = false
_hit_stop(_hit_stop_time_scale, _hit_stop_duration)
Debugger.text("_cooldown_timer", _cooldown_timer, 2)
Debugger.text("_hit_window_timer", _hit_window_timer, 2)
Debugger.vector(
"fghdh",
global_position,
(
global_position
+ global_basis.z.rotated(Vector3.UP, _attack_max_angle) * attack_radius
)
)
Debugger.vector(
"fghdh2",
global_position,
(
global_position
+ global_basis.z.rotated(Vector3.UP, -_attack_max_angle) * attack_radius
)
)
for dir_angle: float in _direction_angles.keys():
Debugger.line(
"fghdh3" + str(dir_angle),
global_position,
(
global_position
+ (
global_basis.z.rotated(
Vector3.UP, dir_angle * (-1.0 if side == Side.LEFT else 1.0)
)
* attack_radius
)
),
Color.BLUE
)
func _physics_process(_delta: float) -> void:
monitoring = _hit_window_timer > 0
func is_hitting() -> bool:
return _cooldown_timer > 0
func attack() -> void:
if _cooldown_timer > 0:
return
_cooldown_timer = _cooldown_time
_hit_window_timer = _hit_window_time
side = Side.LEFT if side == Side.RIGHT else Side.RIGHT
attacked.emit()
func _hit_projectile(projectile: Projectile) -> void:
var diff := projectile.global_position - global_position
diff.y = 0
var angle := global_basis.z.signed_angle_to(diff, Vector3.UP)
Debugger.vector("ASDSAD", global_position, global_position + global_basis.z)
Debugger.vector("ASDSAD2", global_position, global_position + diff)
Debugger.text("angle", rad_to_deg(angle), 2)
if angle > _attack_max_angle or angle < -_attack_max_angle:
return
var angle_sign := -1.0 if side == Side.RIGHT else 1.0
var angle_signed := angle * angle_sign
Debugger.text("side", Side.find_key(side), 2)
Debugger.text("angle_signed", rad_to_deg(angle_signed), 2)
var prev_dir_angle: float = -_attack_max_angle
for dir_angle: float in _direction_angles.keys():
if angle_signed > prev_dir_angle and angle_signed <= dir_angle:
Debugger.text("prev_dir_angle", rad_to_deg(prev_dir_angle), 2)
Debugger.text("dir_angle", rad_to_deg(dir_angle), 2)
var new_direction := global_basis.z.rotated(
Vector3.UP, (_direction_angles[dir_angle] as float) * angle_sign
)
Debugger.vector(
"ASDSAD3",
projectile.global_position,
projectile.global_position + new_direction
)
projectile.hit(new_direction * _hit_projectile_speed)
_queue_hit_stop = true
break
prev_dir_angle = dir_angle
func _set_collision_size(radius: float) -> void:
_attack_shape.radius = radius
func _hit_stop(time_scale: float, duration: float) -> void:
Engine.time_scale = time_scale
await get_tree().create_timer(time_scale * duration).timeout
Engine.time_scale = 1
func _on_area_entered(node: Node3D) -> void:
if _hit_window_timer <= 0:
return
if node is Projectile:
_hit_projectile(node as Projectile)
did_hit.emit()

View File

@@ -0,0 +1 @@
uid://dmu2tkt0wo7d1

View File

@@ -0,0 +1,50 @@
class_name PlayerCursor
extends CanvasLayer
@export var _side_change_speed: float = 15
@export var _screen_inset: float = 100
@export_group("References")
@export var _base: Control
@export var _bat: Control
@export var _arrow: Control
var _side: float = 0
func handle_cursor(player: Player, delta: float) -> void:
var cursor_pos_world := player.attack.global_position + player.aimer.aim_offset
var cursor_pos_screen := MainCamera.instance.unproject_position(cursor_pos_world)
var clamp_corner_min := Vector2.ZERO
var clamp_corner_max := get_viewport().get_visible_rect().size
if player.input_mode_is(Inputer.Mode.CONTROLLER):
clamp_corner_min += Vector2(_screen_inset, _screen_inset)
clamp_corner_max -= Vector2(_screen_inset, _screen_inset)
_base.position = (
cursor_pos_screen.clamp(clamp_corner_min, clamp_corner_max) - _base.size / 2
)
_side = lerpf(
_side as float,
(PI / 2.0) * (1.0 if player.attack.side == PlayerAttacker.Side.LEFT else -1.0),
_side_change_speed * delta
)
var aim_offset_normalized := player.aimer.aim_offset.normalized()
var bat_rotation_point_screen := MainCamera.instance.unproject_position(
cursor_pos_world + aim_offset_normalized.rotated(Vector3.UP, _side as float)
)
var arrow_rotation_point_screen := MainCamera.instance.unproject_position(
cursor_pos_world + aim_offset_normalized
)
_bat.rotation = cursor_pos_screen.angle_to_point(bat_rotation_point_screen)
_arrow.rotation = (
cursor_pos_screen.angle_to_point(arrow_rotation_point_screen) + PI / 2
)
_base.modulate = player.cursor_color
if player.input_mode_is(Inputer.Mode.KB_MOUSE):
_base.position = (get_viewport().get_mouse_position() - _base.size / 2)

View File

@@ -0,0 +1 @@
uid://ddx56siie1sf4

View File

@@ -0,0 +1,108 @@
class_name PlayerMover
extends Node
@export var move_speed: float = 8
@export var move_acceleration: float = 100
@export var move_deceleration: float = 50
@export var fall_speed: float = 20
@export var fall_acceleration: float = 25
var move_input: Vector2
var _move_direction: Vector3
var _move_left: float
var _move_right: float
var _move_up: float
var _move_down: float
func handle_input(event: InputEvent, mode: Inputer.Mode) -> void:
if Player.is_single_player():
return
if mode == Inputer.Mode.KB_MOUSE and event is InputEventKey:
var key_event := event as InputEventKey
if key_event.is_action_pressed("move_left"):
_move_left = 1
elif key_event.is_action_released("move_left"):
_move_left = 0
if key_event.is_action_pressed("move_right"):
_move_right = 1
elif key_event.is_action_released("move_right"):
_move_right = 0
if key_event.is_action_pressed("move_up"):
_move_up = 1
elif key_event.is_action_released("move_up"):
_move_up = 0
if key_event.is_action_pressed("move_down"):
_move_down = 1
elif key_event.is_action_released("move_down"):
_move_down = 0
if mode == Inputer.Mode.CONTROLLER and event is InputEventJoypadMotion:
var motion_event := event as InputEventJoypadMotion
if motion_event.is_action("move_left"):
_move_left = motion_event.get_action_strength("move_left")
if motion_event.is_action("move_right"):
_move_right = motion_event.get_action_strength("move_right")
if motion_event.is_action("move_up"):
_move_up = motion_event.get_action_strength("move_up")
if motion_event.is_action("move_down"):
_move_down = motion_event.get_action_strength("move_down")
func process_movement(
velocity: Vector3, delta: float, is_on_floor: bool, can_move: bool
) -> Vector3:
velocity = _lateral_movement(velocity, delta, can_move)
velocity = _vertical_movement(velocity, delta, is_on_floor)
return velocity
func _lateral_movement(velocity: Vector3, delta: float, can_move: bool) -> Vector3:
if Player.is_single_player():
move_input = Input.get_vector(
"move_left",
"move_right",
"move_up",
"move_down",
Settings.movement_stick_deadzone
)
else:
move_input = Inputer.get_vector_from_raw_strengths(
_move_left,
_move_right,
_move_up,
_move_down,
Settings.movement_stick_deadzone
)
Debugger.text("move_input" + str(get_instance_id()), move_input)
if move_input.length() > 0 and can_move:
_move_direction = Vector3(move_input.x, 0, move_input.y).normalized().rotated(
Vector3.UP, MainCamera.instance.rotation.y
)
var new_velocity := _move_direction * move_speed
new_velocity.y = velocity.y
velocity = velocity.move_toward(new_velocity, move_acceleration * delta)
else:
var new_velocity := Vector3.ZERO
new_velocity.y = velocity.y
velocity = velocity.move_toward(new_velocity, move_deceleration * delta)
return velocity
func _vertical_movement(velocity: Vector3, delta: float, is_on_floor: bool) -> Vector3:
if not is_on_floor:
var new_velocity := velocity
new_velocity.y = -fall_speed
velocity = velocity.move_toward(new_velocity, fall_acceleration * delta)
else:
velocity.y = 0
return velocity

View File

@@ -0,0 +1 @@
uid://5vgfsrafb8ud

View File

@@ -0,0 +1,11 @@
class_name PlayerStats
extends Node
signal damaged
@export var health: int = 6
func damage() -> void:
health -= 1
damaged.emit()

View File

@@ -0,0 +1 @@
uid://clks186ll0joi

View File

@@ -0,0 +1,94 @@
class_name Projectile
extends Area3D
const HEIGHT: float = 1
const MAX_STRETCH: float = 0.75
@export var _speed_stretch_factor: float = 100
@export var _hit_particles_scene: PackedScene
@export var _destroy_particles_scene: PackedScene
@export_group("References")
@export var _model_base: Node3D
@export var _model_mesh: MeshInstance3D
var _start_position: Vector3
var _velocity: Vector3
var _lifetime: float
var _size: float = 0.25
var _life_timer: float
func _ready() -> void:
_life_timer = _lifetime
global_position = _start_position
body_entered.connect(_on_body_entered)
_model_mesh.rotation = Vector3(
randf_range(0, TAU), randf_range(0, TAU), randf_range(0, TAU)
)
_model_mesh.scale = Vector3.ONE * _size
func _physics_process(delta: float) -> void:
if _life_timer <= 0:
queue_free()
_life_timer -= delta
global_position += _velocity * delta
look_at(global_position + _velocity, Vector3.UP, true)
var speed_squash := clampf(
_velocity.length() / _speed_stretch_factor, 0, MAX_STRETCH
)
_model_base.scale = Vector3(1 - speed_squash, 1 - speed_squash, 1 + speed_squash)
func init(velocity: Vector3, start_position: Vector3, lifetime: float = 10) -> void:
_velocity = velocity
_start_position = start_position
_lifetime = lifetime
func hit(velocity: Vector3) -> void:
var hit_particles := (
_hit_particles_scene.instantiate() as Particles3DSelfDestructing
)
get_tree().get_root().add_child(hit_particles)
hit_particles.init(global_position, velocity)
set_velocity(velocity)
func set_velocity(velocity: Vector3) -> void:
_velocity = velocity
_life_timer = _lifetime
func _destroy() -> void:
var destroy_particles := (
_destroy_particles_scene.instantiate() as Particles3DSelfDestructing
)
get_tree().get_root().add_child(destroy_particles)
destroy_particles.init(global_position)
queue_free()
func _on_body_entered(node: Node3D) -> void:
if node is Player:
queue_free()
var player := node as Player
player.stats.damage()
return
if node is CollisionObject3D:
var collision_node := node as CollisionObject3D
if collision_node.collision_layer & 1:
_destroy()
return
if node is CSGCombiner3D:
var collision_node := node as CSGCombiner3D
if collision_node.collision_layer & 1:
_destroy()
return

View File

@@ -0,0 +1 @@
uid://bbd22tc1scoom