Refactored and documented leech and segment code.
This commit is contained in:
parent
75194e1700
commit
01ee8f051f
4 changed files with 63 additions and 31 deletions
|
|
@ -49,20 +49,20 @@ scale = Vector2(2, 2)
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_vk62e")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_vk62e")]
|
||||||
|
|
||||||
[node name="RayCast2D" type="Area2D" parent="."]
|
[node name="StepChecker" type="Area2D" parent="."]
|
||||||
position = Vector2(248, 31.2)
|
position = Vector2(248, 31.2)
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="StepChecker"]
|
||||||
position = Vector2(0, 25)
|
position = Vector2(0, 25)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
[node name="RayCast2D2" type="Area2D" parent="."]
|
[node name="GroundSensor" type="Area2D" parent="."]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D2"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundSensor"]
|
||||||
position = Vector2(0, 25)
|
position = Vector2(0, 25)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
extends EnemyHurtbox
|
extends EnemyHurtbox
|
||||||
|
# The distance from one end to the other while standing
|
||||||
@export var broadth = 250
|
@export var broadth = 250
|
||||||
@export var move_dir = -1
|
var move_dir = [-1, 1].pick_random()
|
||||||
@export var angular_speed = 0.25
|
const angular_speed = 0.25
|
||||||
|
|
||||||
|
# The angle from the base to the moving end
|
||||||
var angle = 0.0 if move_dir == 1 else PI
|
var angle = 0.0 if move_dir == 1 else PI
|
||||||
|
|
||||||
@onready var segments : Array[Node] = $Segments.get_children()
|
@onready var segments : Array[Node] = $Segments.get_children()
|
||||||
|
|
@ -10,25 +13,37 @@ var dead = false
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
if dead: return
|
if dead: return
|
||||||
if not $RayCast2D2.has_overlapping_bodies():
|
# Fall slowly while not grounded
|
||||||
|
if not $GroundSensor.has_overlapping_bodies():
|
||||||
position += 200 * delta * $EarthAligner.global_from_local(Vector2.DOWN)
|
position += 200 * delta * $EarthAligner.global_from_local(Vector2.DOWN)
|
||||||
|
|
||||||
var y = position.length()
|
var y = position.length()
|
||||||
var ratio = - move_dir * broadth / (2 * y)
|
var ratio = - move_dir * broadth / (2 * y)
|
||||||
|
|
||||||
|
# Due to the curvature of the ground, the leech can not just prescribe a semicircle.
|
||||||
|
# 2 * rot_angle determines how much further than 180° the moving leg has to move.
|
||||||
|
# This agrees with the angle of the arc spanned by the leeches standing ends.
|
||||||
var rot_angle = - 2 * asin(ratio)
|
var rot_angle = - 2 * asin(ratio)
|
||||||
angle -= TAU * delta * angular_speed * move_dir
|
angle -= TAU * delta * angular_speed * move_dir
|
||||||
|
|
||||||
|
# Once the rotation has finished, the other leg is used as the leeches center.
|
||||||
|
# The leeches position has to be updated accordingly.
|
||||||
if(angle > PI + abs(rot_angle) / 2 or angle < - abs(rot_angle) / 2):
|
if(angle > PI + abs(rot_angle) / 2 or angle < - abs(rot_angle) / 2):
|
||||||
angle = fmod(angle + PI, PI)
|
angle = fmod(angle + PI, PI)
|
||||||
position = position.rotated(rot_angle)
|
position = position.rotated(rot_angle)
|
||||||
if dead:
|
|
||||||
return
|
# StepChecker determines whether there is ground for the next step.
|
||||||
|
# Place the StepChecker on the side of the next step to be made.
|
||||||
if(move_dir == 1 and angle < 1 or move_dir == -1 and angle > PI - 1):
|
if(move_dir == 1 and angle < 1 or move_dir == -1 and angle > PI - 1):
|
||||||
$RayCast2D.global_position = position.rotated(rot_angle)
|
$StepChecker.global_position = position.rotated(rot_angle)
|
||||||
$RayCast2D.rotation = rot_angle
|
$StepChecker.rotation = rot_angle
|
||||||
|
|
||||||
|
# At some point of the movement, check whether there is ground to step on, turn around otherwise.
|
||||||
if(move_dir == 1 and angle < 0.5 or move_dir == -1 and angle > PI - 0.5):
|
if(move_dir == 1 and angle < 0.5 or move_dir == -1 and angle > PI - 0.5):
|
||||||
if(not $RayCast2D.has_overlapping_bodies()):
|
if(not $StepChecker.has_overlapping_bodies()):
|
||||||
move_dir *= -1
|
move_dir *= -1
|
||||||
if dead:
|
|
||||||
return
|
# Update the position and rotation according to the end's position
|
||||||
for i in range(segment_count):
|
for i in range(segment_count):
|
||||||
var segment_pos_data = calculate_segment_location_and_rotation(i)
|
var segment_pos_data = calculate_segment_location_and_rotation(i)
|
||||||
if not is_instance_valid(segments[i]):
|
if not is_instance_valid(segments[i]):
|
||||||
|
|
@ -38,7 +53,11 @@ func _process(delta: float) -> void:
|
||||||
|
|
||||||
func calculate_segment_location_and_rotation (i) -> Dictionary:
|
func calculate_segment_location_and_rotation (i) -> Dictionary:
|
||||||
var aerial_end_location = Vector2.from_angle(-angle) * broadth
|
var aerial_end_location = Vector2.from_angle(-angle) * broadth
|
||||||
|
|
||||||
|
# Compute the gravicenter of the standing end, the moving end and the apex of the movement.
|
||||||
var gravicenter = (aerial_end_location + Vector2(0, -broadth) + Vector2.ZERO) / 3
|
var gravicenter = (aerial_end_location + Vector2(0, -broadth) + Vector2.ZERO) / 3
|
||||||
|
|
||||||
|
# Compute the circle through the above gravicenter, the standing end and the moving end.
|
||||||
var ax = gravicenter.x
|
var ax = gravicenter.x
|
||||||
var ay = gravicenter.y
|
var ay = gravicenter.y
|
||||||
var bx = aerial_end_location.x
|
var bx = aerial_end_location.x
|
||||||
|
|
@ -48,31 +67,46 @@ func calculate_segment_location_and_rotation (i) -> Dictionary:
|
||||||
var d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
|
var d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
|
||||||
var ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
|
var ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
|
||||||
var uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d
|
var uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d
|
||||||
var center = Vector2(ux, uy)
|
var circum_center = Vector2(ux, uy)
|
||||||
var radius = center.length()
|
var radius = circum_center.length()
|
||||||
|
|
||||||
|
# Determine the direction of the correct arc between the standing and the moving end
|
||||||
var switch_arc_dir = false
|
var switch_arc_dir = false
|
||||||
|
# When the moving end crosses above the standing end, the circumcenter jumps
|
||||||
|
# from one side of the leech to the other, reverting the direction of the arc
|
||||||
if ux < 0:
|
if ux < 0:
|
||||||
switch_arc_dir = !switch_arc_dir
|
switch_arc_dir = !switch_arc_dir
|
||||||
if center.angle() > 1:
|
|
||||||
|
# For sufficiently large size of circum_center.angle() it can happen that
|
||||||
|
# the sign of the (circum_center - aerial_end_location).angle() flips while
|
||||||
|
# that of circum_center.angle() doesn't, which has to be counteracted.
|
||||||
|
if circum_center.angle() > 1:
|
||||||
switch_arc_dir = !switch_arc_dir
|
switch_arc_dir = !switch_arc_dir
|
||||||
|
|
||||||
var angle1 = - PI + center.angle()
|
var angle1 = - PI + circum_center.angle()
|
||||||
var angle2 = - PI + (center-aerial_end_location).angle()
|
var angle2 = - PI + (circum_center - aerial_end_location).angle()
|
||||||
if(switch_arc_dir):
|
if(switch_arc_dir):
|
||||||
angle1 += TAU
|
angle1 += TAU
|
||||||
|
|
||||||
if radius < 10000000:
|
# In the edge case where the leech is almost a straight line, the circum_center
|
||||||
return {"position": center + radius * Vector2.from_angle((i * angle1 + (segment_count - 1 - i) * angle2)/(segment_count - 1)),
|
# and radius approach infty, leading to numerical errors producing flickering.
|
||||||
"rotation": (i * angle1 + (segment_count - 1 - i) * angle2)/ (segment_count - 1) + sign(ux) * PI/2}
|
# This is ruled out by treating these cases as actual straight lines.
|
||||||
|
if radius > 1000000:
|
||||||
else:
|
|
||||||
return {"position" : Vector2.UP * broadth * i / (segment_count - 1),
|
return {"position" : Vector2.UP * broadth * i / (segment_count - 1),
|
||||||
"rotation" : 3 * PI / 2}
|
"rotation" : 3 * PI / 2}
|
||||||
|
|
||||||
|
# Otherwise, the segments are distributed regularly along the arc.
|
||||||
|
else:
|
||||||
|
var arc_angle = (i * angle1 + (segment_count - 1 - i) * angle2)/(segment_count - 1)
|
||||||
|
return {"position": circum_center + radius * Vector2.from_angle(arc_angle),
|
||||||
|
"rotation": arc_angle + sign(ux) * PI/2}
|
||||||
|
|
||||||
|
|
||||||
func _on_damage_taken(_damage, _dir, _id):
|
func _on_damage_taken(_damage, _dir, _id):
|
||||||
$AudioStreamPlayer2D.play()
|
$AudioStreamPlayer2D.play()
|
||||||
|
|
||||||
func _on_death():
|
func _on_death():
|
||||||
|
# Free all other children while waiting for the death sound to play.
|
||||||
dead = true
|
dead = true
|
||||||
for child in get_children():
|
for child in get_children():
|
||||||
if not child is AudioStreamPlayer2D:
|
if not child is AudioStreamPlayer2D:
|
||||||
|
|
|
||||||
|
|
@ -39,20 +39,20 @@ scale = Vector2(-1, -1)
|
||||||
|
|
||||||
[node name="EarthAligner" parent="." instance=ExtResource("3_0r7dp")]
|
[node name="EarthAligner" parent="." instance=ExtResource("3_0r7dp")]
|
||||||
|
|
||||||
[node name="RayCast2D" type="Area2D" parent="."]
|
[node name="StepChecker" type="Area2D" parent="."]
|
||||||
position = Vector2(248, 31.2)
|
position = Vector2(248, 31.2)
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="StepChecker"]
|
||||||
position = Vector2(0, 14.8)
|
position = Vector2(0, 14.8)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
[node name="RayCast2D2" type="Area2D" parent="."]
|
[node name="GroundSensor" type="Area2D" parent="."]
|
||||||
collision_layer = 0
|
collision_layer = 0
|
||||||
collision_mask = 8
|
collision_mask = 8
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="RayCast2D2"]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="GroundSensor"]
|
||||||
position = Vector2(0, 8.8)
|
position = Vector2(0, 8.8)
|
||||||
shape = SubResource("RectangleShape2D_cq6dk")
|
shape = SubResource("RectangleShape2D_cq6dk")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@ extends Area2D
|
||||||
@onready var player = get_tree().get_root().get_node_or_null("main/Player")
|
@onready var player = get_tree().get_root().get_node_or_null("main/Player")
|
||||||
var damage = 1
|
var damage = 1
|
||||||
|
|
||||||
#signal segment_damaged
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if (player != null and overlaps_body(player)):
|
if (player != null and overlaps_body(player)):
|
||||||
player.hurt(damage, self.global_position-player.global_position)
|
player.hurt(damage, self.global_position-player.global_position)
|
||||||
|
|
||||||
|
# Forward taken damage to the parent leech.
|
||||||
func _on_hurtbox_damaged(dmg : int, dir : Vector2, id):
|
func _on_hurtbox_damaged(dmg : int, dir : Vector2, id):
|
||||||
#segment_damaged.emit(dmg, dir)
|
|
||||||
get_parent().get_parent().hurt(dmg, dir, id)
|
get_parent().get_parent().hurt(dmg, dir, id)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue