Could you please tell me where I can find a tutorial video on how to scale a 3D game character up or down or adjust individual body parts during gameplay? If there are no videos, any other information would be helpful. I'm a beginner and just learning.

Hi,

Scaling whole character should be pretty simple (not sure about collision shapes behavior etc. but in simple cases that should work). Assuming that root node of your character is derived from Node3D you just need to update scale of that node like:

@onready var character_root_node : Node3D = $Character

// Just for example updating on ready, but actually you need to do it when it is required
func _ready():
    character_root_node.scale = Vector3(2.0, 2.0, 2.0) // Set scale to 2 in all dimensions

Note that above will also scale all children, so if you are using ray casts or other shapes, they all will be scaled too.

If you want to update only specific parts of your character then it is more complex. You could probably use similar way if your character body parts are all separate MeshInstance3D nodes, but that may give bad results if you are using skinning and skeleton animations. For more complex cases (like character customization etc.) I would suggest to use shape keys and then you can just adjust given shape key in game.

  • SunVell and 5 others replied to this.
    8 days later

    GlyphTheWolf
    Hello, thanks for the reply. I’ve figured out that issue. It wasn’t working because of my mistake. Now I have a different problem. 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.

    GlyphTheWolf

    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)

      SunVell

      Only part of the script was recognized by the site as actual script code—the rest was saved as plain text. I don’t know why

        GlyphTheWolf

        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)

        GlyphTheWolf

        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)

        GlyphTheWolf

        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)

        GlyphTheWolf

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

        SunVell once you figure it out, @ me.

        but imo, if you have no knowledge on how to manipulate vectors and how to detect collisions using code, its pointles effort.. there is no good tutorial. Like there is no good tutorial on how to make NPC AI seem smart..

        I wanted a combat system similar to mordhau, but as it turns out that its not that easy.. either have custom animations that merge together or some sort of reverse kinematics based pre coded animations.. then you would have to make a specialized skeleton to allow your meshes to do that.. its too much for a nobody/beginner to figure out..

        SunVell Only part of the script was recognized by the site as actual script code—the rest was saved as plain text. I don’t know why

        Don't use the "Insert code" icon. It's not reliable for multiple lines of code. Instead, place ~~~ on lines before and after the code that you copy/paste from the Godot editor.

          DaveTheCoder Don't use the "Insert code" icon. It's not reliable for multiple lines of code.

          There the following non-trivial procedure (ritual).

          1. Insert the code into the message.
          2. Highlight the entire piece of code
          3. Click on “ insert code ” </>.

          In this case the code will be formatted correctly.

          ``` 
          code
          ```