diff --git a/Blender/bull_v11.blend1 b/Blender/bull_v11.blend1 new file mode 100644 index 0000000..39e6d20 Binary files /dev/null and b/Blender/bull_v11.blend1 differ diff --git a/Matador.tscn b/Matador.tscn new file mode 100644 index 0000000..00d24a9 --- /dev/null +++ b/Matador.tscn @@ -0,0 +1,29 @@ +[gd_scene format=3 uid="uid://matador_scene_v1"] + +[ext_resource type="Script" path="res://matador.gd" id="1_matgd"] +[ext_resource type="PackedScene" uid="uid://qix7axtc0lf3" path="res://Blender/matador_v02.fbx" id="2_matfbx"] + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_body"] +radius = 0.25 +height = 1.2 + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_hit"] +radius = 0.45 +height = 1.4 + +[node name="Matador" type="CharacterBody3D"] +script = ExtResource("1_matgd") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0) +shape = SubResource("CapsuleShape3D_body") + +[node name="HitArea" type="Area3D" parent="."] +monitoring = true +monitorable = false + +[node name="CollisionShape3D" type="CollisionShape3D" parent="HitArea"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.85, 0) +shape = SubResource("CapsuleShape3D_hit") + +[node name="matador_v02" parent="." instance=ExtResource("2_matfbx")] diff --git a/Player.tscn b/Player.tscn index db6a829..c519778 100644 --- a/Player.tscn +++ b/Player.tscn @@ -18,6 +18,7 @@ radius = 0.40 radius = 0.30 [node name="Player" type="CharacterBody3D" unique_id=165510044] +collision_layer = 3 script = ExtResource("1_xhfnw") [node name="CollisionChest" type="CollisionShape3D" parent="."] diff --git a/camera_spring_arm.gd b/camera_spring_arm.gd index d683874..f036071 100644 --- a/camera_spring_arm.gd +++ b/camera_spring_arm.gd @@ -1,11 +1,13 @@ extends Node3D @onready var spring_arm: SpringArm3D = $SpringArm3D +var _desired_length: float = 7.0 func _ready() -> void: Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) - spring_arm.spring_length = DP.f("cam_spring_length") + _desired_length = DP.f("cam_spring_length") + spring_arm.spring_length = _desired_length func _unhandled_input(event: InputEvent) -> void: @@ -22,9 +24,9 @@ func _unhandled_input(event: InputEvent) -> void: rotation.x = clamp(rotation.x, min_vert, max_vert) if event.is_action_pressed("zoom_in"): - spring_arm.spring_length = clamp(spring_arm.spring_length - 1.0, min_zoom, max_zoom) + _desired_length = clamp(_desired_length - 1.0, min_zoom, max_zoom) if event.is_action_pressed("zoom_out"): - spring_arm.spring_length = clamp(spring_arm.spring_length + 1.0, min_zoom, max_zoom) + _desired_length = clamp(_desired_length + 1.0, min_zoom, max_zoom) if event.is_action_pressed("toggle_mouse_capture"): if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: @@ -34,4 +36,4 @@ func _unhandled_input(event: InputEvent) -> void: func _process(_delta: float) -> void: - spring_arm.spring_length = DP.f("cam_spring_length") + spring_arm.spring_length = _desired_length diff --git a/matador.gd b/matador.gd new file mode 100644 index 0000000..dc34a8a --- /dev/null +++ b/matador.gd @@ -0,0 +1,161 @@ +extends CharacterBody3D + +enum State { WANDER, RAGDOLL } + +var _state: State = State.WANDER +var _skeleton: Skeleton3D = null +var _sim: PhysicalBoneSimulator3D = null +var _wander_target: Vector3 = Vector3.ZERO +var _idle_timer: float = 0.0 + +@onready var _mesh: Node3D = $matador_v02 +@onready var _hit_area: Area3D = $HitArea + + +func _ready() -> void: + add_to_group(&"matador") + _skeleton = _find_skeleton(_mesh) + if _skeleton: + _sim = _setup_physical_bones() + _hit_area.body_entered.connect(_on_body_entered) + _pick_wander_target() + + +func _physics_process(delta: float) -> void: + if not is_on_floor(): + velocity += get_gravity() * delta + match _state: + State.WANDER: _tick_wander(delta) + State.RAGDOLL: _tick_ragdoll(delta) + + +func _tick_wander(delta: float) -> void: + if _idle_timer > 0.0: + _idle_timer -= delta + velocity.x = move_toward(velocity.x, 0.0, 10.0 * delta) + velocity.z = move_toward(velocity.z, 0.0, 10.0 * delta) + move_and_slide() + return + + var dx: float = _wander_target.x - global_position.x + var dz: float = _wander_target.z - global_position.z + if Vector2(dx, dz).length() < 0.8: + _idle_timer = randf_range(DP.f("mat_idle_min"), DP.f("mat_idle_max")) + _pick_wander_target() + else: + var dir := Vector3(dx, 0.0, dz).normalized() + var spd := DP.f("mat_walk_speed") + velocity.x = dir.x * spd + velocity.z = dir.z * spd + _mesh.rotation.y = lerp_angle( + _mesh.rotation.y, atan2(velocity.x, velocity.z), delta * 8.0) + move_and_slide() + + +func _tick_ragdoll(delta: float) -> void: + velocity.x = move_toward(velocity.x, 0.0, 5.0 * delta) + velocity.z = move_toward(velocity.z, 0.0, 5.0 * delta) + move_and_slide() + + +func _on_body_entered(body: Node3D) -> void: + if _state == State.RAGDOLL: + return + if not body.is_in_group(&"player"): + return + var player := body as CharacterBody3D + if player.velocity.length() < DP.f("mat_hit_threshold"): + return + var hit_dir := (global_position - player.global_position).normalized() + _enter_ragdoll(hit_dir, player.velocity.length()) + + +func _enter_ragdoll(hit_dir: Vector3, bull_speed: float) -> void: + _state = State.RAGDOLL + hit_dir.y = 0.0 + hit_dir = hit_dir.normalized() + velocity = Vector3(hit_dir.x, 0.5, hit_dir.z) * bull_speed * 0.4 + + if not _sim: + return + _sim.active = true + var strength := DP.f("mat_ragdoll_impulse") + for child: Node in _sim.get_children(): + if child is PhysicalBone3D: + var scatter := Vector3( + randf_range(-0.3, 0.3), + randf_range(0.2, 0.8), + randf_range(-0.3, 0.3)) + (child as PhysicalBone3D).apply_central_impulse( + (hit_dir + scatter).normalized() * strength) + + +func _pick_wander_target() -> void: + var radius := DP.f("mat_wander_radius") + var angle := randf() * TAU + var dist := randf_range(2.0, radius) + _wander_target = Vector3(cos(angle) * dist, 0.0, sin(angle) * dist) + + +# ── Ragdoll setup ───────────────────────────────────────────────────────────── + +func _setup_physical_bones() -> PhysicalBoneSimulator3D: + for child: Node in _skeleton.get_children(): + if child is PhysicalBoneSimulator3D: + (child as PhysicalBoneSimulator3D).active = false + return child as PhysicalBoneSimulator3D + + var sim := PhysicalBoneSimulator3D.new() + sim.active = false + _skeleton.add_child(sim) + + # [bone_name, sphere_radius] or [bone_name, capsule_radius, capsule_height] + const BONES: Array = [ + ["matador", 0.18], + ["chest", 0.13, 0.30], + ["head", 0.14], + ["arm_L", 0.07, 0.22], + ["arm_R", 0.07, 0.22], + ["forearm_L", 0.06, 0.22], + ["forearm_R", 0.06, 0.22], + ["leg_L", 0.10, 0.32], + ["leg_R", 0.10, 0.32], + ["shin_L", 0.08, 0.28], + ["shin_R", 0.08, 0.28], + ] + + for entry: Array in BONES: + var bname: String = entry[0] + if _skeleton.find_bone(bname) == -1: + continue + var pb := PhysicalBone3D.new() + pb.bone_name = bname + pb.linear_damp = 0.5 + pb.angular_damp = 0.5 + var cs := CollisionShape3D.new() + var use_capsule: bool = entry.size() >= 3 + if use_capsule: + var cap := CapsuleShape3D.new() + cap.radius = entry[1] + cap.height = entry[2] + cs.shape = cap + else: + var sph := SphereShape3D.new() + sph.radius = entry[1] + cs.shape = sph + pb.add_child(cs) + sim.add_child(pb) + + return sim + + +# ── Utility ─────────────────────────────────────────────────────────────────── + +func _find_skeleton(node: Node) -> Skeleton3D: + if node is Skeleton3D: + return node as Skeleton3D + for child: Node in node.get_children(): + var r := _find_skeleton(child) + if r: + return r + return null diff --git a/matador_spawn.gd b/matador_spawn.gd new file mode 100644 index 0000000..becea9a --- /dev/null +++ b/matador_spawn.gd @@ -0,0 +1,17 @@ +extends Node3D + +const MATADOR := preload("res://Matador.tscn") + + +func _ready() -> void: + _spawn.call_deferred() + + +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) + get_parent().add_child(m)