2024-11-11 11:39:12 -07:00
|
|
|
class_name GameBall extends RigidBody3D
|
|
|
|
## Base class for all gfolf balls
|
|
|
|
|
2024-11-20 12:27:22 -07:00
|
|
|
## Fired as soon as this ball enters a water hazard
|
2024-11-11 11:39:12 -07:00
|
|
|
signal entered_water
|
2024-11-12 18:16:30 -07:00
|
|
|
|
2024-11-20 19:22:11 -07:00
|
|
|
## Types of game balls
|
|
|
|
enum Type {
|
|
|
|
NONE,
|
|
|
|
BASIC,
|
|
|
|
PLASMA,
|
|
|
|
}
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
const TERRAIN_DAMPING_EPSILON := 1e-6
|
2024-11-21 02:19:00 -07:00
|
|
|
const MAGNUS_EPSILON := 1e-3
|
2024-11-12 20:03:05 -07:00
|
|
|
|
2024-11-12 18:16:30 -07:00
|
|
|
## Angular damping while in air
|
|
|
|
@export var air_damping := 0.0
|
|
|
|
## Angular damping while in collision with rough terrain
|
|
|
|
@export var rough_damping := 8.0
|
2024-11-12 20:03:05 -07:00
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
#@export var fluid_density := 1.225
|
|
|
|
#@export var lift_coefficient := 0.05
|
|
|
|
#@export var radius := 0.05
|
|
|
|
## Coefficient of angular velocity influence on linear velocity
|
|
|
|
## This is approximately 1/2 * rho * C_L * pi * r^2
|
|
|
|
## where `rho` is the fluid density of the medium, or 1.225 for air at sea level,
|
|
|
|
## and `C_L` is the lift coefficient which for our purposes is 0.05,
|
|
|
|
## and `r` is the radius of the ball, which is 5cm.
|
|
|
|
@export var magnus_coefficient := 0.00024
|
|
|
|
|
2024-12-04 17:59:44 -07:00
|
|
|
## Causes the ball to stick to surfaces
|
|
|
|
@export var magnetic := false
|
2024-11-12 20:03:05 -07:00
|
|
|
|
2024-11-18 12:57:11 -07:00
|
|
|
## Base damage inflicted on impact with a player
|
|
|
|
@export var base_damage := 15.0
|
|
|
|
|
2024-11-21 18:33:02 -07:00
|
|
|
## Scaling factor for additional force-based damage
|
|
|
|
@export var damage_force_scale := 0.01
|
|
|
|
|
2024-11-19 23:26:18 -07:00
|
|
|
var _last_contact_normal: Vector3 = Vector3.UP
|
|
|
|
var _position_on_last_wake: Vector3
|
|
|
|
var _awake := false
|
2024-11-12 20:03:05 -07:00
|
|
|
var _zones: Array[BallZone] = []
|
|
|
|
|
2024-12-04 17:59:44 -07:00
|
|
|
@onready var manual_sleep_timer: Timer = %ManualSleepTimer
|
2024-11-22 20:04:14 -07:00
|
|
|
@onready var sfx: BallSFX = %SFX
|
|
|
|
|
2024-11-17 14:07:28 -07:00
|
|
|
@onready var normal_physics: PhysicsMaterial = preload(
|
|
|
|
"res://src/equipment/balls/physics_ball/normal_physics.tres"
|
|
|
|
)
|
2024-11-12 20:03:05 -07:00
|
|
|
|
2024-11-19 21:52:04 -07:00
|
|
|
@onready var _debug_draw: Control = %DebugDraw
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
|
2024-12-04 17:59:44 -07:00
|
|
|
## Should this ball stick to surfaces, rather than bounce?
|
|
|
|
func is_sticky() -> bool:
|
|
|
|
return magnetic
|
|
|
|
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
## Called by a water area when this ball enters it
|
|
|
|
func enter_water() -> void:
|
|
|
|
entered_water.emit()
|
|
|
|
|
|
|
|
|
2024-11-21 18:33:02 -07:00
|
|
|
func get_damage() -> float:
|
|
|
|
print("velocity: ", linear_velocity.length())
|
|
|
|
return base_damage + linear_velocity.length_squared() * damage_force_scale
|
|
|
|
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
func _total_terrain_angular_damping() -> float:
|
|
|
|
return _zones.reduce(
|
|
|
|
func(a: float, b: BallZone) -> float: return a + b.terrain_angular_damping, 0.0
|
|
|
|
)
|
2024-11-12 18:16:30 -07:00
|
|
|
|
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
func _magnus_force() -> Vector3:
|
|
|
|
return magnus_coefficient * angular_velocity.cross(linear_velocity)
|
|
|
|
|
|
|
|
|
2024-11-12 18:16:30 -07:00
|
|
|
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
|
2024-11-19 23:26:18 -07:00
|
|
|
if not _awake:
|
|
|
|
# Triggered on first frame after waking
|
|
|
|
_awake = true
|
|
|
|
_position_on_last_wake = global_position
|
|
|
|
_last_contact_normal = Vector3.UP
|
2024-11-21 02:19:00 -07:00
|
|
|
# TODO something's fucky here... I think this gets called once after the ball sleeps
|
2024-11-19 23:26:18 -07:00
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
var damping := air_damping
|
2024-11-19 23:26:18 -07:00
|
|
|
if state.get_contact_count():
|
2024-11-21 02:19:00 -07:00
|
|
|
constant_force = Vector3.ZERO
|
|
|
|
|
2024-11-20 22:04:35 -07:00
|
|
|
# We want the contact normal which minimizes the angle to the up vector
|
|
|
|
var min_dot := -1.0
|
|
|
|
for i: int in range(state.get_contact_count()):
|
|
|
|
var norm := state.get_contact_local_normal(i)
|
|
|
|
var dot := norm.dot(Vector3.UP)
|
|
|
|
if dot > min_dot:
|
|
|
|
min_dot = dot
|
|
|
|
_last_contact_normal = norm
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
damping = _total_terrain_angular_damping()
|
|
|
|
if damping <= TERRAIN_DAMPING_EPSILON:
|
|
|
|
damping = rough_damping
|
|
|
|
angular_damp = damping
|
|
|
|
|
|
|
|
|
2024-11-21 02:19:00 -07:00
|
|
|
func _physics_process(_delta: float) -> void:
|
|
|
|
# Simulate magnus effect
|
|
|
|
var magnus := _magnus_force()
|
|
|
|
if magnus.length_squared() > MAGNUS_EPSILON:
|
|
|
|
apply_central_force(magnus)
|
|
|
|
|
|
|
|
|
2024-11-12 20:03:05 -07:00
|
|
|
func enter_zone(zone: BallZone) -> void:
|
|
|
|
_zones.push_back(zone)
|
|
|
|
|
|
|
|
if zone.water_hazard:
|
|
|
|
entered_water.emit()
|
|
|
|
|
|
|
|
|
|
|
|
func exit_zone(zone: BallZone) -> void:
|
|
|
|
_zones.erase(zone)
|
2024-11-19 23:26:18 -07:00
|
|
|
|
|
|
|
|
|
|
|
func get_reoriented_basis() -> Basis:
|
|
|
|
var up := _last_contact_normal.normalized()
|
|
|
|
var forward := (_position_on_last_wake - global_position).normalized()
|
|
|
|
var right := up.cross(forward).normalized()
|
|
|
|
forward = right.cross(up) # orthonormalize
|
|
|
|
return Basis(right, up, forward)
|
|
|
|
|
|
|
|
|
|
|
|
func _on_sleeping_state_changed() -> void:
|
2024-11-20 12:27:22 -07:00
|
|
|
print("SLEEPING STATE: ", sleeping)
|
2024-11-19 23:26:18 -07:00
|
|
|
if sleeping:
|
|
|
|
# Trigger to reassign on wake
|
|
|
|
_awake = false
|
2024-11-22 20:04:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
func _on_collision(body: Node) -> void:
|
2024-12-04 17:59:44 -07:00
|
|
|
if is_sticky():
|
|
|
|
# Freeze physics as soon as we hit something
|
|
|
|
freeze = true
|
|
|
|
manual_sleep_timer.start()
|
|
|
|
|
2024-11-22 20:04:14 -07:00
|
|
|
if body is Terrain3D:
|
2024-11-22 21:15:54 -07:00
|
|
|
sfx.play_sfx(Terrain.at_position(global_position, body as Terrain3D))
|
2024-11-22 20:04:14 -07:00
|
|
|
elif body is CSGShape3D:
|
2024-11-22 21:15:54 -07:00
|
|
|
sfx.play_sfx(Terrain.from_physical_layer((body as CSGShape3D).collision_layer))
|
2024-11-22 20:04:14 -07:00
|
|
|
elif body is CollisionObject3D:
|
2024-11-22 21:15:54 -07:00
|
|
|
sfx.play_sfx(Terrain.from_physical_layer((body as CollisionObject3D).collision_layer))
|
2024-11-22 20:04:14 -07:00
|
|
|
else:
|
|
|
|
print_debug("COLLIDER: ", body)
|
2024-12-04 17:59:44 -07:00
|
|
|
|
|
|
|
|
|
|
|
func _fire_sleep_signal() -> void:
|
|
|
|
sleeping = true
|
|
|
|
sleeping_state_changed.emit()
|