generated from krampus/template-godot4
	Shot curve using a simulated Magnus effect
This commit is contained in:
		
							parent
							
								
									d129a410b8
								
							
						
					
					
						commit
						e723e0c022
					
				@ -12,6 +12,7 @@ enum Type {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TERRAIN_DAMPING_EPSILON := 1e-6
 | 
			
		||||
const MAGNUS_EPSILON := 1e-3
 | 
			
		||||
const IRON_DAMPING := 9999.0
 | 
			
		||||
 | 
			
		||||
## Angular damping while in air
 | 
			
		||||
@ -21,6 +22,16 @@ const IRON_DAMPING := 9999.0
 | 
			
		||||
## Angular damping for iron balls
 | 
			
		||||
@export var iron_damping := 9999.0
 | 
			
		||||
 | 
			
		||||
#@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
 | 
			
		||||
 | 
			
		||||
## Causes the ball to act more like a brick
 | 
			
		||||
@export var iron_ball := false:
 | 
			
		||||
	set(value):
 | 
			
		||||
@ -59,15 +70,22 @@ func _total_terrain_angular_damping() -> float:
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _magnus_force() -> Vector3:
 | 
			
		||||
	return magnus_coefficient * angular_velocity.cross(linear_velocity)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
 | 
			
		||||
	if not _awake:
 | 
			
		||||
		# Triggered on first frame after waking
 | 
			
		||||
		_awake = true
 | 
			
		||||
		_position_on_last_wake = global_position
 | 
			
		||||
		_last_contact_normal = Vector3.UP
 | 
			
		||||
		# TODO something's fucky here... I think this gets called once after the ball sleeps
 | 
			
		||||
 | 
			
		||||
	var damping := air_damping
 | 
			
		||||
	if state.get_contact_count():
 | 
			
		||||
		constant_force = Vector3.ZERO
 | 
			
		||||
 | 
			
		||||
		# 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()):
 | 
			
		||||
@ -85,6 +103,13 @@ func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
 | 
			
		||||
	angular_damp = damping
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _physics_process(_delta: float) -> void:
 | 
			
		||||
	# Simulate magnus effect
 | 
			
		||||
	var magnus := _magnus_force()
 | 
			
		||||
	if magnus.length_squared() > MAGNUS_EPSILON:
 | 
			
		||||
		apply_central_force(magnus)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func enter_zone(zone: BallZone) -> void:
 | 
			
		||||
	_zones.push_back(zone)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,8 +34,13 @@ script/source = "extends Control
 | 
			
		||||
const COLOR_X := Color(1, 0, 0)
 | 
			
		||||
const COLOR_Y := Color(0, 1, 0)
 | 
			
		||||
const COLOR_Z := Color(0, 0, 1)
 | 
			
		||||
const COLOR_MAGNUS := Color.CYAN
 | 
			
		||||
const MAGNUS_SCALE := 3
 | 
			
		||||
const WIDTH := 4
 | 
			
		||||
 | 
			
		||||
@export var draw_reoriented_basis := true
 | 
			
		||||
@export var draw_magnus_effect := true
 | 
			
		||||
 | 
			
		||||
@onready var physics_ball: GameBall = $\"..\"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -45,9 +50,9 @@ func _process(_delta: float) -> void:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _draw() -> void:
 | 
			
		||||
	if physics_ball._last_contact_normal != null:
 | 
			
		||||
		var camera := get_viewport().get_camera_3d()
 | 
			
		||||
		var start := camera.unproject_position(physics_ball.global_position)
 | 
			
		||||
	var camera := get_viewport().get_camera_3d()
 | 
			
		||||
	var start := camera.unproject_position(physics_ball.global_position)
 | 
			
		||||
	if draw_reoriented_basis and physics_ball._last_contact_normal != null:
 | 
			
		||||
		var basis := physics_ball.get_reoriented_basis()
 | 
			
		||||
		var end_x := camera.unproject_position(physics_ball.global_position + basis.x)
 | 
			
		||||
		var end_y := camera.unproject_position(physics_ball.global_position + basis.y)
 | 
			
		||||
@ -55,6 +60,10 @@ func _draw() -> void:
 | 
			
		||||
		draw_line(start, end_x, COLOR_X, WIDTH)
 | 
			
		||||
		draw_line(start, end_y, COLOR_Y, WIDTH)
 | 
			
		||||
		draw_line(start, end_z, COLOR_Z, WIDTH)
 | 
			
		||||
 | 
			
		||||
	if draw_magnus_effect:
 | 
			
		||||
		var end := camera.unproject_position(physics_ball.global_position + physics_ball._magnus_force() * MAGNUS_SCALE)
 | 
			
		||||
		draw_line(start, end, COLOR_MAGNUS, WIDTH)
 | 
			
		||||
"
 | 
			
		||||
 | 
			
		||||
[node name="PhysicsBall" type="RigidBody3D"]
 | 
			
		||||
@ -86,5 +95,6 @@ anchor_bottom = 1.0
 | 
			
		||||
grow_horizontal = 2
 | 
			
		||||
grow_vertical = 2
 | 
			
		||||
script = SubResource("GDScript_p4v7o")
 | 
			
		||||
draw_reoriented_basis = false
 | 
			
		||||
 | 
			
		||||
[connection signal="sleeping_state_changed" from="." to="." method="_on_sleeping_state_changed"]
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,12 @@ const BIG_POWER_THRESHOLD := 0.7
 | 
			
		||||
## Amount of life lost when landing in water
 | 
			
		||||
const WATER_DAMAGE := 10.0
 | 
			
		||||
 | 
			
		||||
## Angle of influence that shot curve has, in radians
 | 
			
		||||
const CURVE_INFLUENCE := PI / 8
 | 
			
		||||
 | 
			
		||||
## Maximum absolute curve for the "nice shot" animation to play
 | 
			
		||||
const NICE_THRESHOLD := 0.2
 | 
			
		||||
 | 
			
		||||
## In Driving Range mode, the ball can be retrieved in the shot phase.
 | 
			
		||||
@export var driving_range := false
 | 
			
		||||
 | 
			
		||||
@ -108,7 +114,7 @@ var shot_curve: float:
 | 
			
		||||
	set(value):
 | 
			
		||||
		hud.curve_bar.value = value
 | 
			
		||||
	get:
 | 
			
		||||
		return hud.power_bar.value
 | 
			
		||||
		return hud.curve_bar.value
 | 
			
		||||
 | 
			
		||||
var game_ball: GameBall:
 | 
			
		||||
	get:
 | 
			
		||||
@ -231,13 +237,22 @@ func take_shot() -> void:
 | 
			
		||||
 | 
			
		||||
	print_debug("WHACK!\nPower: ", shot_power, "\nCurve: ", shot_curve)
 | 
			
		||||
 | 
			
		||||
	# Momentary impulse applied to the ball
 | 
			
		||||
	var impulse := get_shot_impulse(shot_power)
 | 
			
		||||
	print_debug("Shot impulse: ", impulse, "; ", impulse.length(), " N*s")
 | 
			
		||||
 | 
			
		||||
	var curve := shot_ref.global_basis.x.normalized() * shot_curve
 | 
			
		||||
 | 
			
		||||
	# Position where the ball is hit (imparts spin)
 | 
			
		||||
	var offset := curve * 0.001
 | 
			
		||||
	print_debug("Shot offset: ", offset, "; ", offset.length(), " m")
 | 
			
		||||
	# Rotate impulse
 | 
			
		||||
	impulse = impulse.rotated(Vector3.UP, -shot_curve * CURVE_INFLUENCE)
 | 
			
		||||
 | 
			
		||||
	if game_ball:
 | 
			
		||||
		game_ball.iron_ball = club == Club.Type.IRON
 | 
			
		||||
		game_ball.freeze = false
 | 
			
		||||
		game_ball.apply_central_impulse(impulse)
 | 
			
		||||
		game_ball.apply_impulse(impulse, offset)
 | 
			
		||||
 | 
			
		||||
	# Use a ball if a limited type is selected
 | 
			
		||||
	if player.get_balls(ball_type) > 0:
 | 
			
		||||
@ -407,7 +422,11 @@ func _on_phase_change(new_phase: Phase) -> void:
 | 
			
		||||
			hud.power_bar.hide()
 | 
			
		||||
			hud.curve_bar.hide()
 | 
			
		||||
 | 
			
		||||
			hud.play_nice_animation()
 | 
			
		||||
			if perfect_aim:
 | 
			
		||||
				shot_curve = 0.0
 | 
			
		||||
 | 
			
		||||
			if abs(shot_curve) <= NICE_THRESHOLD:
 | 
			
		||||
				hud.play_nice_animation()
 | 
			
		||||
 | 
			
		||||
			if not driving_range:
 | 
			
		||||
				shot_animation.play("shoot")
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user