R to reset matador
This commit is contained in:
+1
-7
@@ -2,7 +2,6 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://bn777gguclud5" path="res://player.gd" id="1_xhfnw"]
|
||||
[ext_resource type="Script" uid="uid://b4td4k8yo5y78" path="res://camera_spring_arm.gd" id="2_kpjcp"]
|
||||
[ext_resource type="Script" uid="uid://b720l6m0ehrj3" path="res://camera_follow.gd" id="3_kne1u"]
|
||||
[ext_resource type="PackedScene" uid="uid://qy6qrxdfgx2g" path="res://Assets/bull_v10.fbx" id="4_e80uo"]
|
||||
|
||||
[sub_resource type="SphereShape3D" id="SphereShape3D_kne1u"]
|
||||
@@ -43,13 +42,8 @@ shape = SubResource("SphereShape3D_kne1u")
|
||||
spring_length = 7.0
|
||||
margin = 0.5
|
||||
|
||||
[node name="SpringPosition" type="Node3D" parent="SpringArmPivot/SpringArm3D" unique_id=1307131677]
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="SpringArmPivot" unique_id=1120161087 node_paths=PackedStringArray("spring_arm")]
|
||||
[node name="Camera3D" type="Camera3D" parent="SpringArmPivot/SpringArm3D" unique_id=1120161087]
|
||||
keep_aspect = 0
|
||||
script = ExtResource("3_kne1u")
|
||||
spring_arm = NodePath("../SpringArm3D/SpringPosition")
|
||||
lerp_power = 6.0
|
||||
|
||||
[node name="bull_v10" parent="." unique_id=1386495166 instance=ExtResource("4_e80uo")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.655, 0.23)
|
||||
|
||||
+9
-6
@@ -80,19 +80,22 @@ func _register_all() -> void:
|
||||
_reg_f("Camera", "cam_min_vert_angle", -60.0, -90.0, 0.0, 1.0)
|
||||
_reg_f("Camera", "cam_max_vert_angle", 45.0, 0.0, 90.0, 1.0)
|
||||
# ── Matador ───────────────────────────────────────────────────────────────
|
||||
_reg_f("Matador", "mat_spawn_count", 8.0, 1.0, 30.0, 1.0)
|
||||
_reg_f("Matador", "mat_spawn_count", 1.0, 1.0, 30.0, 1.0)
|
||||
_reg_f("Matador", "mat_walk_speed", 2.5, 0.5, 10.0)
|
||||
_reg_f("Matador", "mat_wander_radius", 22.0, 5.0, 50.0)
|
||||
_reg_f("Matador", "mat_idle_min", 0.5, 0.0, 5.0, 0.1)
|
||||
_reg_f("Matador", "mat_idle_max", 2.5, 0.5, 10.0, 0.1)
|
||||
_reg_f("Matador", "mat_hit_threshold", 4.0, 1.0, 30.0)
|
||||
_reg_f("Matador", "mat_ragdoll_impulse", 3.0, 0.5, 20.0)
|
||||
_reg_f("Matador", "mat_ragdoll_impulse", 4.0, 0.5, 20.0)
|
||||
_reg_f("Matador", "mat_flee_range", 10.0, 2.0, 40.0)
|
||||
_reg_f("Matador", "mat_flee_speed", 4.0, 0.5, 12.0)
|
||||
_reg_f("Matador", "mat_dodge_range", 3.5, 1.0, 10.0)
|
||||
_reg_f("Matador", "mat_dodge_speed", 7.0, 1.0, 20.0)
|
||||
_reg_f("Matador", "mat_dodge_duration", 0.5, 0.1, 2.0, 0.05)
|
||||
_reg_f("Matador", "mat_dodge_cooldown", 1.5, 0.0, 5.0, 0.1)
|
||||
_reg_f("Matador", "mat_charge_speed", 6.0, 1.0, 30.0)
|
||||
_reg_f("Matador", "mat_brace_range", 8.0, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_brace_duration", 0.4, 0.05, 1.5, 0.05)
|
||||
_reg_f("Matador", "mat_commit_dist", 2.5, 0.5, 6.0, 0.1)
|
||||
_reg_f("Matador", "mat_step_speed", 9.0, 2.0, 20.0)
|
||||
_reg_f("Matador", "mat_step_duration", 0.35, 0.1, 1.0, 0.05)
|
||||
_reg_f("Matador", "mat_dodge_cooldown", 3.0, 0.5, 8.0, 0.25)
|
||||
# ── Debug ─────────────────────────────────────────────────────────────────
|
||||
_reg_b("Debug", "show_collisions", false)
|
||||
|
||||
|
||||
+169
-37
@@ -1,6 +1,11 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
enum State { WANDER, FLEE, DODGE, RAGDOLL }
|
||||
# WANDER → bull outside flee range, roaming freely
|
||||
# FLEE → bull within range, matador backs away
|
||||
# BRACE → charge detected: plant feet, face the bull (cite phase)
|
||||
# SIDESTEP→ bull commits: sharp lateral step to let it pass (pase phase)
|
||||
# RAGDOLL → hit by bull at speed
|
||||
enum State { WANDER, FLEE, BRACE, SIDESTEP, RAGDOLL }
|
||||
|
||||
var _state: State = State.WANDER
|
||||
var _skeleton: Skeleton3D = null
|
||||
@@ -9,8 +14,9 @@ var _anim_player: AnimationPlayer = null
|
||||
var _wander_target: Vector3 = Vector3.ZERO
|
||||
var _idle_timer: float = 0.0
|
||||
var _bull: CharacterBody3D = null
|
||||
var _dodge_dir: Vector3 = Vector3.ZERO
|
||||
var _dodge_timer: float = 0.0
|
||||
var _step_dir: Vector3 = Vector3.ZERO
|
||||
var _brace_timer: float = 0.0
|
||||
var _step_timer: float = 0.0
|
||||
var _dodge_cd: float = 0.0
|
||||
|
||||
@onready var _mesh: Node3D = $matador_v02
|
||||
@@ -55,7 +61,8 @@ func _physics_process(delta: float) -> void:
|
||||
match _state:
|
||||
State.WANDER: _tick_wander(delta)
|
||||
State.FLEE: _tick_flee(delta)
|
||||
State.DODGE: _tick_dodge(delta)
|
||||
State.BRACE: _tick_brace(delta)
|
||||
State.SIDESTEP: _tick_sidestep(delta)
|
||||
State.RAGDOLL: _tick_ragdoll(delta)
|
||||
|
||||
|
||||
@@ -66,13 +73,27 @@ func _update_ai_state() -> void:
|
||||
if dist < DP.f("mat_flee_range"):
|
||||
_state = State.FLEE
|
||||
State.FLEE:
|
||||
if dist < DP.f("mat_dodge_range") and _dodge_cd <= 0.0:
|
||||
_start_dodge()
|
||||
if _dodge_cd <= 0.0 and (dist < DP.f("mat_commit_dist") or _is_charge_incoming()):
|
||||
_start_brace()
|
||||
elif dist > DP.f("mat_flee_range") * 1.3:
|
||||
_state = State.WANDER
|
||||
_pick_wander_target()
|
||||
State.DODGE:
|
||||
pass
|
||||
State.BRACE:
|
||||
pass # brace timer drives transition
|
||||
State.SIDESTEP:
|
||||
pass # step timer drives transition
|
||||
|
||||
|
||||
# Bull is moving fast and aimed within ~50° of the matador
|
||||
func _is_charge_incoming() -> bool:
|
||||
var bull_flat := Vector3(_bull.velocity.x, 0.0, _bull.velocity.z)
|
||||
if bull_flat.length() < DP.f("mat_charge_speed"):
|
||||
return false
|
||||
var to_me := (global_position - _bull.global_position)
|
||||
to_me.y = 0.0
|
||||
if to_me.length() > DP.f("mat_brace_range"):
|
||||
return false
|
||||
return bull_flat.normalized().dot(to_me.normalized()) > 0.65
|
||||
|
||||
|
||||
func _tick_wander(delta: float) -> void:
|
||||
@@ -114,33 +135,57 @@ func _tick_flee(delta: float) -> void:
|
||||
move_and_slide()
|
||||
|
||||
|
||||
func _start_dodge() -> void:
|
||||
_state = State.DODGE
|
||||
var bull_vel_flat := Vector3(_bull.velocity.x, 0.0, _bull.velocity.z)
|
||||
var bull_fwd: Vector3
|
||||
if bull_vel_flat.length() > 1.0:
|
||||
bull_fwd = bull_vel_flat.normalized()
|
||||
# Cite phase: plant feet, face the bull — wait for it to commit
|
||||
func _start_brace() -> void:
|
||||
_state = State.BRACE
|
||||
var bull_flat := Vector3(_bull.velocity.x, 0.0, _bull.velocity.z)
|
||||
# Use bull velocity if it's moving, else use bull→matador direction as proxy
|
||||
var charge_dir: Vector3
|
||||
if bull_flat.length() > 1.0:
|
||||
charge_dir = bull_flat.normalized()
|
||||
else:
|
||||
bull_fwd = (_bull.global_position - global_position)
|
||||
bull_fwd.y = 0.0
|
||||
bull_fwd = bull_fwd.normalized()
|
||||
# Step sideways relative to the bull's travel direction, biased slightly backward
|
||||
charge_dir = (_bull.global_position - global_position)
|
||||
charge_dir.y = 0.0
|
||||
charge_dir = charge_dir.normalized()
|
||||
# Decide which side to step to now, so the choice is fixed before the bull arrives
|
||||
var side := 1.0 if randf() > 0.5 else -1.0
|
||||
var perp := bull_fwd.rotated(Vector3.UP, PI * 0.5 * side)
|
||||
_dodge_dir = (perp * 0.7 + (-bull_fwd) * 0.3).normalized()
|
||||
_dodge_timer = DP.f("mat_dodge_duration")
|
||||
_step_dir = charge_dir.rotated(Vector3.UP, PI * 0.5 * side)
|
||||
_brace_timer = DP.f("mat_brace_duration")
|
||||
|
||||
|
||||
func _tick_dodge(delta: float) -> void:
|
||||
_dodge_timer -= delta
|
||||
if _dodge_timer <= 0.0:
|
||||
func _tick_brace(delta: float) -> void:
|
||||
# Decelerate to a stop
|
||||
velocity.x = move_toward(velocity.x, 0.0, 18.0 * delta)
|
||||
velocity.z = move_toward(velocity.z, 0.0, 18.0 * delta)
|
||||
# Face the bull
|
||||
var to_bull := (_bull.global_position - global_position)
|
||||
to_bull.y = 0.0
|
||||
if to_bull.length() > 0.1:
|
||||
_mesh.rotation.y = lerp_angle(
|
||||
_mesh.rotation.y, atan2(to_bull.x, to_bull.z), delta * 14.0)
|
||||
_play_anim(_IDLE_ANIM)
|
||||
move_and_slide()
|
||||
|
||||
_brace_timer -= delta
|
||||
var dist := global_position.distance_to(_bull.global_position)
|
||||
# Execute the step when the bull is almost on top or the brace window expires
|
||||
if dist < DP.f("mat_commit_dist") or _brace_timer <= 0.0:
|
||||
_state = State.SIDESTEP
|
||||
_step_timer = DP.f("mat_step_duration")
|
||||
|
||||
|
||||
# Pase phase: sharp lateral step as the bull commits to its line
|
||||
func _tick_sidestep(delta: float) -> void:
|
||||
_step_timer -= delta
|
||||
if _step_timer <= 0.0:
|
||||
_state = State.FLEE
|
||||
_dodge_cd = DP.f("mat_dodge_cooldown")
|
||||
return
|
||||
var spd := DP.f("mat_dodge_speed")
|
||||
velocity.x = _dodge_dir.x * spd
|
||||
velocity.z = _dodge_dir.z * spd
|
||||
_mesh.rotation.y = lerp_angle(_mesh.rotation.y, atan2(velocity.x, velocity.z), delta * 12.0)
|
||||
var spd := DP.f("mat_step_speed")
|
||||
velocity.x = _step_dir.x * spd
|
||||
velocity.z = _step_dir.z * spd
|
||||
# Snap facing quickly to sell the movement
|
||||
_mesh.rotation.y = lerp_angle(_mesh.rotation.y, atan2(velocity.x, velocity.z), delta * 20.0)
|
||||
_play_anim(_WALK_ANIM)
|
||||
move_and_slide()
|
||||
|
||||
@@ -151,6 +196,36 @@ func _tick_ragdoll(_delta: float) -> void:
|
||||
move_and_slide()
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not (event is InputEventKey):
|
||||
return
|
||||
var key := event as InputEventKey
|
||||
if key.keycode == KEY_R and key.pressed and not key.echo:
|
||||
_reset()
|
||||
|
||||
|
||||
func _reset() -> void:
|
||||
_state = State.WANDER
|
||||
velocity = Vector3.ZERO
|
||||
collision_layer = 1
|
||||
global_position = Vector3(0.0, 1.0, 0.0)
|
||||
|
||||
if _sim:
|
||||
_sim.physical_bones_stop_simulation()
|
||||
_sim.active = false
|
||||
|
||||
if _anim_player and _anim_player.has_animation(_IDLE_ANIM):
|
||||
_anim_player.stop()
|
||||
_anim_player.play(_IDLE_ANIM)
|
||||
_anim_player.seek(0.0, true)
|
||||
|
||||
_dodge_cd = 0.0
|
||||
_brace_timer = 0.0
|
||||
_step_timer = 0.0
|
||||
_idle_timer = 0.0
|
||||
_pick_wander_target()
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if _state == State.RAGDOLL:
|
||||
return
|
||||
@@ -159,7 +234,9 @@ func _on_body_entered(body: Node3D) -> void:
|
||||
var player := body as CharacterBody3D
|
||||
if player.velocity.length() < DP.f("mat_hit_threshold"):
|
||||
return
|
||||
var hit_dir := (global_position - player.global_position).normalized()
|
||||
var flat_vel := Vector3(player.velocity.x, 0.0, player.velocity.z)
|
||||
var hit_dir := flat_vel.normalized() if flat_vel.length() > 0.5 else \
|
||||
(global_position - player.global_position).normalized()
|
||||
_enter_ragdoll(hit_dir, player.velocity.length())
|
||||
|
||||
|
||||
@@ -167,6 +244,10 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
||||
_state = State.RAGDOLL
|
||||
hit_dir.y = 0.0
|
||||
hit_dir = hit_dir.normalized()
|
||||
# Arc upward so the body lifts before falling — feels like a real goring throw
|
||||
var throw_dir := (hit_dir + Vector3(0.0, 0.5, 0.0)).normalized()
|
||||
|
||||
_spawn_blood_burst(hit_dir)
|
||||
|
||||
# Keep the CharacterBody3D stationary: physics bones simulate relative to the
|
||||
# skeleton root, so sliding the root makes every bone pose diverge.
|
||||
@@ -191,17 +272,67 @@ func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void:
|
||||
var bone_idx := _skeleton.find_bone(pb.bone_name)
|
||||
if bone_idx >= 0:
|
||||
pb.global_transform = _skeleton.global_transform * _skeleton.get_bone_global_pose(bone_idx)
|
||||
pb.simulate_physics = true
|
||||
_sim.active = true
|
||||
var strength := DP.f("mat_ragdoll_impulse")
|
||||
_sim.physical_bones_start_simulation()
|
||||
var strength := DP.f("mat_ragdoll_impulse") * clampf(bull_speed / 15.0, 0.4, 1.3)
|
||||
for child: Node in _sim.get_children():
|
||||
if child is PhysicalBone3D:
|
||||
var scatter := Vector3(
|
||||
randf_range(-0.4, 0.4),
|
||||
randf_range(0.1, 0.5),
|
||||
randf_range(-0.4, 0.4))
|
||||
randf_range(-0.12, 0.12),
|
||||
randf_range(-0.05, 0.08),
|
||||
randf_range(-0.12, 0.12))
|
||||
(child as PhysicalBone3D).apply_central_impulse(
|
||||
(hit_dir + scatter).normalized() * strength)
|
||||
(throw_dir + scatter).normalized() * strength)
|
||||
|
||||
|
||||
# ── Blood burst ───────────────────────────────────────────────────────────────
|
||||
|
||||
func _spawn_blood_burst(hit_dir: Vector3) -> void:
|
||||
var p := CPUParticles3D.new()
|
||||
get_tree().current_scene.add_child(p)
|
||||
p.global_position = global_position + Vector3(0.0, 0.9, 0.0)
|
||||
|
||||
var mat := StandardMaterial3D.new()
|
||||
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
mat.vertex_color_use_as_albedo = true
|
||||
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
||||
|
||||
var sphere := SphereMesh.new()
|
||||
sphere.radius = 0.07
|
||||
sphere.height = 0.14
|
||||
sphere.radial_segments = 4
|
||||
sphere.rings = 2
|
||||
sphere.material = mat
|
||||
|
||||
var ramp := Gradient.new()
|
||||
ramp.set_color(0, Color(1.0, 0.02, 0.02, 1.0))
|
||||
ramp.set_color(1, Color(0.25, 0.0, 0.0, 0.0))
|
||||
|
||||
var scale_curve := Curve.new()
|
||||
scale_curve.add_point(Vector2(0.0, 1.0))
|
||||
scale_curve.add_point(Vector2(0.6, 0.5))
|
||||
scale_curve.add_point(Vector2(1.0, 0.0))
|
||||
|
||||
p.mesh = sphere
|
||||
p.color_ramp = ramp
|
||||
p.scale_amount_curve = scale_curve
|
||||
p.one_shot = true
|
||||
p.explosiveness = 0.9
|
||||
p.amount = 60
|
||||
p.lifetime = 1.1
|
||||
p.randomness = 0.4
|
||||
p.local_coords = false
|
||||
p.direction = (hit_dir * 0.6 + Vector3.UP * 0.4).normalized()
|
||||
p.spread = 80.0
|
||||
p.gravity = Vector3(0.0, -7.0, 0.0)
|
||||
p.initial_velocity_min = 4.0
|
||||
p.initial_velocity_max = 11.0
|
||||
p.scale_amount_min = 0.5
|
||||
p.scale_amount_max = 1.6
|
||||
p.emitting = true
|
||||
|
||||
get_tree().create_timer(p.lifetime + 0.3).timeout.connect(p.queue_free)
|
||||
|
||||
|
||||
# ── Animation helpers ─────────────────────────────────────────────────────────
|
||||
@@ -273,8 +404,9 @@ func _setup_physical_bones() -> PhysicalBoneSimulator3D:
|
||||
var pb := PhysicalBone3D.new()
|
||||
pb.bone_name = bname
|
||||
pb.joint_type = PhysicalBone3D.JOINT_TYPE_PIN
|
||||
pb.linear_damp = 3.0
|
||||
pb.angular_damp = 4.0
|
||||
pb.mass = 0.4
|
||||
pb.linear_damp = 0.4
|
||||
pb.angular_damp = 0.8
|
||||
pb.collision_layer = 1
|
||||
pb.collision_mask = 1
|
||||
var cs := CollisionShape3D.new()
|
||||
|
||||
+1
-3
@@ -11,7 +11,5 @@ func _spawn() -> void:
|
||||
var count: int = int(DP.f("mat_spawn_count"))
|
||||
for i: int in count:
|
||||
var m: Node3D = MATADOR.instantiate()
|
||||
var angle := randf() * TAU
|
||||
var dist := randf_range(6.0, DP.f("mat_wander_radius"))
|
||||
m.position = Vector3(cos(angle) * dist, 1.0, sin(angle) * dist)
|
||||
m.position = Vector3(0.0, 1.0, 0.0)
|
||||
get_parent().add_child(m)
|
||||
|
||||
Reference in New Issue
Block a user