203 lines
5.8 KiB
GDScript
203 lines
5.8 KiB
GDScript
class_name Player
|
|
extends CharacterBody3D
|
|
|
|
|
|
signal bounced()
|
|
signal shot()
|
|
signal charge_canceled()
|
|
|
|
|
|
@export_group("Visuals")
|
|
@export var goal_animation_time: float = 1.0
|
|
@export var charge_gradient: Gradient
|
|
@export var power_line_material: StandardMaterial3D
|
|
|
|
@export_group("Movement")
|
|
@export var gravity: float
|
|
@export var friction: float
|
|
@export var friction_coef: float
|
|
@export var friction_pow: float
|
|
@export var power_scale: float
|
|
@export var power_sensitivity: float
|
|
@export var power_threshold: float
|
|
@export var stop_threshold: float
|
|
|
|
@export_group("Camera", "camera_")
|
|
@export_range(0,90,1,"radians_as_degrees") var camera_low_angle: float
|
|
@export_range(0,90,1,"radians_as_degrees") var camera_high_angle: float
|
|
@export_range(0,90,0.5,"radians_as_degrees") var camera_yaw_sensitivity: float
|
|
@export_range(0,90,0.5,"radians_as_degrees") var camera_pitch_sensitivity: float
|
|
|
|
@export_group("Node References")
|
|
@export var state_chart: StateChart
|
|
@export var graphics: Node3D
|
|
@export var power_indicator: Node3D
|
|
@export var camera_arm: SpringArm3D
|
|
@export var collision_shape: CollisionShape3D
|
|
|
|
|
|
var power: float = 0.0:
|
|
set(value):
|
|
power = clampf(value, 0.0, 1.0)
|
|
|
|
var charging_power: bool = false
|
|
var prev_velocity: Vector3 = Vector3.ZERO
|
|
|
|
var _entered_goal: Node3D = null
|
|
|
|
|
|
#region Builtin Overrides
|
|
func _ready() -> void:
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
prev_velocity = velocity
|
|
move_and_slide()
|
|
state_chart.set_expression_property(&"velocity", velocity)
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if event.is_action_pressed(&"charge_shot"):
|
|
state_chart.send_event(&"charge_pressed")
|
|
if event.is_action_released(&"charge_shot"):
|
|
state_chart.send_event(&"charge_released")
|
|
|
|
if event is InputEventMouseMotion:
|
|
camera_arm.rotate_y(event.screen_relative.x * camera_yaw_sensitivity)
|
|
|
|
if charging_power:
|
|
power += event.screen_relative.y * power_sensitivity
|
|
else:
|
|
camera_arm.rotation.x -= event.screen_relative.y * camera_pitch_sensitivity
|
|
camera_arm.rotation.x = clampf(camera_arm.rotation.x, -camera_high_angle, -camera_low_angle)
|
|
#endregion
|
|
|
|
|
|
#region Public Functions
|
|
func enter_goal(goal: GoalPost) -> void:
|
|
_entered_goal = goal
|
|
state_chart.send_event(&"goal_entered")
|
|
|
|
|
|
func attach_to_pole(pole: WatermanPole) -> void:
|
|
_attached_pole = pole
|
|
state_chart.send_event(&"pole_attached")
|
|
#endregion
|
|
|
|
|
|
#region Charging
|
|
func _start_charge() -> void:
|
|
charging_power = true
|
|
power_indicator.visible = true
|
|
power_indicator.scale.z = 0.0
|
|
power = 0.0
|
|
|
|
func _update_charge(_delta: float) -> void:
|
|
power_indicator.scale.z = power
|
|
var camera_z = get_viewport().get_camera_3d().global_transform.basis.z
|
|
camera_z.y = 0.0
|
|
power_indicator.look_at(power_indicator.global_position + camera_z)
|
|
power_line_material.albedo_color = charge_gradient.sample(power)
|
|
|
|
func _end_charge() -> void:
|
|
charging_power = false
|
|
power_indicator.visible = false
|
|
if power >= power_threshold:
|
|
var camera_z = get_viewport().get_camera_3d().global_transform.basis.z
|
|
camera_z.y = 0.0
|
|
velocity = -camera_z.normalized() * power * power_scale
|
|
prev_velocity = velocity
|
|
_bounce_on_walls(1.0/60.0)
|
|
shot.emit()
|
|
else:
|
|
charge_canceled.emit()
|
|
#endregion
|
|
|
|
|
|
#region Moving
|
|
func _apply_gravity(delta: float) -> void:
|
|
velocity.y -= gravity * delta
|
|
|
|
func _slow_to_stop(delta: float) -> void:
|
|
if is_on_floor():
|
|
var new_velocity = velocity * Vector3(1.0, 0.0, 1.0)
|
|
|
|
#new_velocity = lerp(new_velocity, Vector3.ZERO, friction_coef * delta)
|
|
#new_velocity = lerp(
|
|
#new_velocity, Vector3.ZERO,
|
|
#power_scale * pow(friction_coef / new_velocity.length(), friction_pow * delta)
|
|
#)
|
|
new_velocity = new_velocity.move_toward(Vector3.ZERO, friction * delta)
|
|
if new_velocity.length_squared() <= stop_threshold * stop_threshold:
|
|
new_velocity = Vector3.ZERO
|
|
velocity.x = new_velocity.x
|
|
velocity.z = new_velocity.z
|
|
|
|
func _bounce_on_walls(delta: float = 0.0) -> void:
|
|
var h_vel = (prev_velocity * Vector3(1.0, 0.0, 1.0))
|
|
var col = move_and_collide(h_vel * delta, true)
|
|
if col:
|
|
if col.get_angle() > floor_max_angle:
|
|
h_vel = h_vel.bounce(col.get_normal())
|
|
velocity.x = h_vel.x
|
|
velocity.z = h_vel.z
|
|
bounced.emit()
|
|
#endregion
|
|
|
|
|
|
#region Winning
|
|
func _start_winning() -> void:
|
|
velocity = Vector3.ZERO
|
|
prev_velocity = velocity
|
|
collision_shape.disabled = true
|
|
|
|
var tween = create_tween()
|
|
tween.set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
|
|
tween.tween_property(graphics, ^"scale", Vector3.ZERO, goal_animation_time)
|
|
tween.set_parallel(true)
|
|
tween.tween_property(graphics, ^"global_position", _entered_goal.global_position, goal_animation_time)
|
|
#endregion
|
|
|
|
|
|
#region Pole Spinning
|
|
var _attached_pole: WatermanPole = null
|
|
var _pole_angle: float = 0.0
|
|
var _pole_stored_speed: float = 0.0
|
|
|
|
func _start_pole_spin() -> void:
|
|
_pole_stored_speed = flatten_vector(velocity).length()
|
|
velocity = Vector3.ZERO
|
|
var pole_xz = flatten_vector(_attached_pole.global_position)
|
|
var self_xz = flatten_vector(global_position)
|
|
_pole_angle = Vector3.FORWARD.angle_to(self_xz - pole_xz)
|
|
|
|
func _process_pole_spin(delta: float) -> void:
|
|
# rise
|
|
global_position.y += _attached_pole.rise_speed * delta
|
|
global_position.y = clampf(
|
|
global_position.y,
|
|
_attached_pole.global_position.y,
|
|
_attached_pole.top.global_position.y
|
|
)
|
|
|
|
# spin
|
|
_pole_angle += _attached_pole.spin_speed * delta
|
|
var pole_xz = flatten_vector(_attached_pole.global_position)
|
|
var self_dir = Vector3.FORWARD.rotated(Vector3.UP, _pole_angle)
|
|
var self_xz = pole_xz + self_dir * _attached_pole.offset
|
|
global_position.x = self_xz.x
|
|
global_position.z = self_xz.z
|
|
|
|
func _end_pole_spin() -> void:
|
|
var pole_xz = flatten_vector(_attached_pole.global_position)
|
|
var impulse = Vector3.FORWARD.rotated(Vector3.UP, _pole_angle) * _pole_stored_speed
|
|
velocity.x = impulse.x
|
|
velocity.z = impulse.z
|
|
#endregion
|
|
|
|
|
|
#region Helpers
|
|
func flatten_vector(vector: Vector3) -> Vector3:
|
|
return Vector3(vector.x, 0.0, vector.z)
|
|
#endregion
|