I want to make a game with mechanics like in Half Sword:

I’m trying to implement this using AI, but so far, it’s not working out. It’s only getting worse and worse. I’m not sure if I’m even heading in the right direction. Do you know why it’s not working? Or maybe there’s a tutorial or some learning material on similar mechanics somewhere?

I’m attaching the original screenshots from before any changes related to Half Sword mechanics. After applying the AI’s suggested changes, things just got more and more confusing. It completely rewrote the script, and I’m not very skilled with scripting yet. So I just blindly trusted it and copied its new scripts. I’m also attaching the final state of the scenes and scripts as they are now.

Player script before implementing Half Sword mechanics:

extends CharacterBody3D

const DMG_PART = preload("res://particles/dmg_part.tscn")
@onready var area_dmg: Area3D = $Skin/AreaDMG

var run = false
const WALK_SPEED = 7.0
const SPRINT_SPEED = 10.0
var current_speed = WALK_SPEED
const JUMP_VELOCITY = 4.5
@onready var animation_player: AnimationPlayer = $Skin/AnimationPlayer
@onready var skin: Node3D = $Skin
@onready var camera_point: Node3D = $CameraPoint
var attack = false

var type = "player"
var dmg = 10
var hp = 10

var sens_hor = 0.2
var sens_ver = 0.2
var camera_pitch := 0.0
const MAX_PITCH = 50
const MIN_PITCH = -50

Новые переменные для масштабирования рук

@onready var skeleton: Skeleton3D = $Skin/Skeleton3D
var hand_scale: float = 1.0
const MAX_HAND_SCALE: float = 5.0
const SCALE_INCREMENT: float = 0.1
var bones_to_scale = [
"mixamorig_LeftArm",
"mixamorig_LeftForeArm",
"mixamorig_LeftHand",
"mixamorig_RightArm",
"mixamorig_RightForeArm",
"mixamorig_RightHand"
]

func _ready() -> void:
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
reset_hand_scale()

func reset_hand_scale():
hand_scale = 1.0
update_hand_scale()

func update_hand_scale():
for bone_name in bones_to_scale:
var bone_id = skeleton.find_bone(bone_name)
if bone_id != -1:
var basis = Basis().scaled(Vector3(hand_scale, hand_scale, hand_scale))
skeleton.set_bone_pose_scale(bone_id, basis.get_scale())

func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
rotate_y(deg_to_rad(-event.relative.x * sens_hor))
skin.rotate_y(deg_to_rad(event.relative.x * sens_hor))
camera_pitch -= event.relative.y * sens_ver
camera_pitch = clamp(camera_pitch, MIN_PITCH, MAX_PITCH)
camera_point.rotation.x = deg_to_rad(camera_pitch)

func _physics_process(delta: float) -> void:
if attack:
if not animation_player.is_playing():
attack = false
else:
return

# Проверяем нажатие Shift для спринта
var is_sprinting = Input.is_action_pressed("sprint") and run

if is_sprinting:
	current_speed = SPRINT_SPEED
else:
	current_speed = WALK_SPEED

# Управление анимациями
if !run:
	animation_player.play("mixamo_com")
elif is_sprinting:
	animation_player.play("Fast Run/mixamo_com")
else:
	animation_player.play("Drunk Run Forward in Place/mixamo_com")

# Гравитация
if not is_on_floor():
	velocity += get_gravity() * delta
	
# Обработка атаки
if Input.is_action_just_pressed("attack") and is_on_floor() and not attack:
	attack = true
	animation_player.play("Uppercut Jab/mixamo_com")
	increase_hand_scale()
	attack_action()

# Прыжок
if Input.is_action_just_pressed("ui_accept") and is_on_floor():
	velocity.y = JUMP_VELOCITY

# Движение
var input_dir := Input.get_vector("left", "right", "top", "down")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

if direction.length() > 0:
	run = true
	skin.look_at(global_transform.origin - direction, Vector3.UP)
	velocity.x = direction.x * current_speed
	velocity.z = direction.z * current_speed
else:
	run = false
	velocity.x = move_toward(velocity.x, 0, current_speed)
	velocity.z = move_toward(velocity.z, 0, current_speed)

move_and_slide()

func increase_hand_scale():
if hand_scale < MAX_HAND_SCALE:
hand_scale = min(hand_scale + SCALE_INCREMENT, MAX_HAND_SCALE)
update_hand_scale()

func take_dmg(dmg):
hp -= dmg
var dmg_part_inst = DMG_PART.instantiate()
add_child(dmg_part_inst)
dmg_part_inst.restart()
reset_hand_scale()

func attack_action():
var overlapping_bodies = area_dmg.get_overlapping_bodies()
for body in overlapping_bodies:
if body.has_method("take_dmg"):
body.take_dmg(dmg)

Level script before implementing Half Sword mechanics:

extends Node3D

var min_x = -23
var max_x = 24
var min_z = -24
var max_z = 24

const DEMON_GIRL = preload("res://enemys/demon_girl/demon_girl.tscn")
@onready var player: CharacterBody3D = $Player

#Called when the node enters the scene tree for the first time.
func _ready() -> void:
create_enemy(DEMON_GIRL)
pass # Replace with function body.

Called every frame. 'delta' is the elapsed time since the previous frame.

func _process(delta: float) -> void:
pass

func create_enemy(mob):
var mob_inst = mob.instantiate()

add_child(mob_inst)

var random_x = randf_range(min_x, max_x)
var random_z = randf_range(min_z, max_z)
var random_position = Vector3(random_x, player.global_position.y, random_z)

mob_inst.global_position = random_position

mob_inst.set_target_point(player)

Player script after implementing Half Sword mechanics:

extends CharacterBody3D

Настройки движения

const WALK_SPEED = 4.0
const SPRINT_SPEED = 6.5
const JUMP_FORCE = 8.0
const MOUSE_SENSITIVITY = 0.1

Настройки рук

const HAND_FOLLOW_SPEED = 8.0
const PUNCH_FORCE_MULTIPLIER = 80.0

Ноды

@onready var skeleton = $Skin/Skeleton3D
@onready var camera = $CameraPoint/Camera3D
@onready var left_hand_area = $Skin/Skeleton3D/LeftHandBone/LeftHandArea
@onready var right_hand_area = $Skin/Skeleton3D/RightHandBone/RightHandArea

Переменные

var current_hand_pos = Vector3.ZERO
var is_punching = false
var punch_start_pos = Vector2.ZERO
var mouse_velocity = Vector2.ZERO

func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
setup_physical_bones()

func setup_physical_bones():

Создаем физические кости если их нет

if skeleton.get_child_count() == 0:
skeleton.create_physical_skeleton()

# Настраиваем только для рук
for bone in skeleton.get_children():
	if bone is PhysicalBone3D and "Hand" in bone.name:
		bone.gravity_scale = 0.2
		bone.linear_damp = 4.0
		bone.angular_damp = 4.0
		bone.mass = 1.2

func _input(event):
if event is InputEventMouseMotion:
handle_camera_rotation(event)
mouse_velocity = event.relative

if event.is_action_pressed("left_click"):
	start_punch(true)
elif event.is_action_pressed("right_click"):
	start_punch(false)
elif event.is_action_released("left_click") or event.is_action_released("right_click"):
	end_punch()

func handle_camera_rotation(event):
rotate_y(-event.relative.x * MOUSE_SENSITIVITY)
camera.rotate_x(-event.relative.y * MOUSE_SENSITIVITY)
camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-70), deg_to_rad(70))

func _physics_process(delta):
handle_movement(delta)
update_hands(delta)
move_and_slide()

func handle_movement(delta):
var input_dir = Input.get_vector("left", "right", "forward", "backward")
var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

var speed = SPRINT_SPEED if Input.is_action_pressed("sprint") else WALK_SPEED
velocity.x = direction.x * speed
velocity.z = direction.z * speed

if is_on_floor() and Input.is_action_just_pressed("jump"):
	velocity.y = JUMP_FORCE

if not is_on_floor():
	velocity.y -= get_gravity() * delta

func update_hands(delta):
if is_punching:
update_punch()
else:
update_hands_idle(delta)

func update_hands_idle(delta):
var target_pos = Vector3(
mouse_velocity.x * 0.001,
-mouse_velocity.y * 0.001,
-0.3
)
current_hand_pos = current_hand_pos.lerp(target_pos, delta * HAND_FOLLOW_SPEED)
apply_hand_positions()

func start_punch(is_left):
is_punching = true
punch_start_pos = get_viewport().get_mouse_position()
if is_left:
left_hand_area.monitoring = true
else:
right_hand_area.monitoring = true

func update_punch():
var current_mouse_pos = get_viewport().get_mouse_position()
var punch_direction = (current_mouse_pos - punch_start_pos).normalized()
var punch_power = clamp(punch_direction.length() * 0.3, 0.2, 1.0)

current_hand_pos = Vector3(
	punch_direction.x * punch_power,
	-punch_direction.y * punch_power,
	-0.2
)
apply_hand_positions()

func end_punch():
is_punching = false
left_hand_area.monitoring = false
right_hand_area.monitoring = false
mouse_velocity = Vector2.ZERO

func apply_hand_positions():
for hand in ["LeftHand", "RightHand"]:
var bone_id = skeleton.find_bone("mixamorig_" + hand)
if bone_id != -1:
skeleton.set_bone_pose_position(bone_id, current_hand_pos)

func _on_hand_area_body_entered(body):
if body.has_method("take_damage"):
var force_direction = -global_transform.basis.z
var force_strength = clamp(mouse_velocity.length() * 0.05, 5.0, 20.0)
body.take_damage(force_strength, force_direction)

Level script after implementing Half Sword mechanics:

extends Node3D

Замените на правильный путь к вашей сцене врага!

@export var enemy_scene: PackedScene = preload("res://enemies/demon_girl/demon_girl.tscn")

@onready var player = $Player

func _ready():
if enemy_scene != null:
spawn_enemies(3)
else:
printerr("Ошибка: Не назначена сцена врага (enemy_scene)")

func spawn_enemies(count):
for i in range(count):
var enemy = enemy_scene.instantiate()
add_child(enemy)

	# Случайная позиция вокруг игрока
	var angle = randf_range(0, 2 * PI)
	var distance = randf_range(3.0, 6.0)
	enemy.position = player.position + Vector3(
		cos(angle) * distance,
		0,
		sin(angle) * distance
	)
	
	if enemy.has_method("set_target"):
		enemy.set_target(player)

Here are the bones. It's a free model—I downloaded it from the internet.



I´m sorry that I can´t help you at all; but I´ll give one advice, zip the whole project and add a download link for anyone interested into having a look at it.