Godot Version
Godot_v4.7.1-stable (Windows 64-bit)
Context:
I want to make a hand movement system. After holding down a button a hand moves towards a point that is dictated by the mouse. The distance between the a shoulder of the player and the hand must not go over a certain circular radius. The hand is basically locked in a circle and cannot move out of it. This hand must move in a way which allows godot physics to handle collisions, it must also be able to collide with other rigidbodies, staticbodies and such (this is so i can push objects with it). That means the hand position cannot be set, something like velocity must be used. The hand must be a child of a certain node and cannot be top level, as it must use local position relative to its parent, because im planning to animate the parent, which will also allow the hand to be animated (the child of said parent). The hand must move along two axis, yet the player has the ability to rotate.
PLEASE suggest a valid way to make something like this work, hinges, skeleton, rigidbody2d, etc. If youre wondering, i have tried all of them myself but failed miserably mostly due to two main issues:
Actual Issues:
-
All joints are not fixed. Whenever i move anything with velocity it has a tendency to lag behind or go too far. Ive tried tinkering with the damping to limit this but it has not worked. Ive tried to find an alternative joint on the asset store, a joint that allows free rotation but keeps the nodes stuck together with no lagging behind, however that either doesnt seem to exist or i simply cant find it.
-
The script ive previously made allowed for movement, however i was unable to animate it via parent due to the nodes being top-level to allow physics to work properly.
I will post code and examples i currently have set up, you do not need to follow them, but it should suit as good reference to tell you what i want code-wise.
CODE:
extends Node3D
#ARM MOVEMENT SCRIPT
@export_group("Initialization")
@onready var Center: Node3D = get_node("Center")
@onready var Test: Node3D = get_node("Mouse")
@export var Sensitivity: float = 0.005
@export var MaxRadius: float = 1.0
@export var Speed: float = 5.0 # Control how aggressively it snaps to the mouse position
@export_group("Left")
@onready var LeftShoulder: Node3D = get_node("LeftShoulder")
@export var HandL: RigidBody3D
@export_group("Right")
@onready var RightShoulder: Node3D = get_node("RightShoulder")
@export var HandR: RigidBody3D
#how much i wanna move by
var MoveTo := Vector2.ZERO
func _ready() -> void:
Test.top_level = true
HandL.top_level = true
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
#ive also realised this script doesnt really work when the player (mitchel) turns
#however im currently just trying to connect the arms properly
func _physics_process(_delta: float) -> void:
## test
#MoveTo += Global.MouseVel * Sensitivity
#Global.MouseVel = Vector2.ZERO
#
#if MoveTo.length() > MaxRadius:
#MoveTo = MoveTo.limit_length(MaxRadius)
#
#var local_x_axis: Vector3 = Center.global_transform.basis.x
#var local_y_axis: Vector3 = Center.global_transform.basis.y
#
#var local_offset: Vector3 = (local_x_axis * MoveTo.x) + (local_y_axis * MoveTo.y)
#var target_pos: Vector3 = Center.global_position + local_offset
#
#var displacement: Vector3 = target_pos - Test.global_position
#
#Test.sleeping = false
#Test.linear_velocity = displacement * Speed
#
## rotate testblock the same as mitchel
#Test.global_rotation.y = Center.global_rotation.y
pass
HandL.global_rotation.y = LeftShoulder.global_rotation.y
if Input.is_action_pressed("RightClick"):
MoveTo += Global.MouseVel * Sensitivity
#gets the vector its suppose to move by, based on mouse inputs in the global script
Global.MouseVel = Vector2.ZERO
#manually setting velocity to 0 so it doesnt stay
if MoveTo.length() > MaxRadius:
MoveTo = MoveTo.limit_length(MaxRadius)
#checks if the vector is too large and "clamps" it
var MoveTo3 = Vector3(MoveTo.x, MoveTo.y, 0)
#converts the vector2 to vector3 so i can put it in linear_velocity
HandL.sleeping = false
HandL.linear_velocity = MoveTo3* Speed
#actually moves the hand
HandR.global_rotation.y = RightShoulder.global_rotation.y
if Input.is_action_pressed("LeftClick"):
MoveTo += Global.MouseVel * Sensitivity
Global.MouseVel = Vector2.ZERO
if MoveTo.length() > MaxRadius:
MoveTo = MoveTo.limit_length(MaxRadius)
var MoveTo3 = Vector3(MoveTo.x, MoveTo.y, 0)
HandR.sleeping = false
HandR.linear_velocity = MoveTo3 * Speed
extends CharacterBody3D
#GENERAL MOVEMENT SCRIPT (currently working)
@export var Collision: CollisionShape3D
@export var MitchelAnim: AnimatedSprite3D
@export var WalkSound: AudioStreamPlayer3D
@export var EventAnim: AnimationPlayer
@export var ArmAnim: AnimationPlayer
@export var FloorDetect: RayCast3D
@export var RoofDetect: RayCast3D
var IsCrouching:bool = false
var IsMoving:bool = false
var AlreadyJ:bool = false
const YOffset = 0.1
const CHeight = 1.667
const JUMP_VELOCITY = 3
var SPEED : float = 1.0
var target_rotation_y : float = 0.0
var meta = ""
func animHandle():
if not is_on_floor():
setHeight(1)
MitchelAnim.animation = "Crouch"
if !AlreadyJ:
playSound()
AlreadyJ = true
elif IsCrouching and IsMoving:
setHeight(1)
MitchelAnim.animation = "CrouchWalk"
EventAnim.play("CWalk")
elif IsCrouching and not IsMoving:
setHeight(1)
MitchelAnim.animation = "Crouch"
EventAnim.stop()
elif IsMoving and not IsCrouching:
setHeight(2)
MitchelAnim.animation = "Walk"
#ArmAnim.play("Walk")
EventAnim.play("Walk")
else:
setHeight(2)
MitchelAnim.animation = "Idle"
#ArmAnim.play("Idle")
EventAnim.stop()
func playSound():
if FloorDetect.get_collider() != null:
print(FloorDetect.get_collider())
meta = FloorDetect.get_collider().get_meta("Type")
if meta != null and meta != "Ignore":
WalkSound.stream = load("res://Mitchel2/FootstepSound/" + meta + ".mp3")
elif meta == null:
WalkSound.stream = load("res://Mitchel2/FootstepSound/Concrete.mp3")
else:
print(FloorDetect.get_collider())
WalkSound.stream = preload("res://Mitchel2/FootstepSound/Concrete.mp3")
WalkSound.pitch_scale = randf_range(0.9,1.1)
WalkSound.play()
func setHeight(Height):
match Height:
1:
MitchelAnim.position.y = YOffset
Collision.shape.size.y = CHeight
2:
MitchelAnim.position.y = YOffset*0
Collision.shape.size.y = CHeight*1.27414517097
func _process(delta: float) -> void:
# Update the target angle when pressed
if Input.is_action_just_pressed("RightRotate"):
target_rotation_y += deg_to_rad(90)
elif Input.is_action_just_pressed("LeftRotate"):
target_rotation_y -= deg_to_rad(90)
# Smoothly interpolate towards target_rotation_y every frame
rotation.y = lerp_angle(rotation.y, target_rotation_y, delta * 10.0)
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump and crouching.
if Input.is_action_just_pressed("Crouch") and RoofDetect.get_collider() == null:
IsCrouching = !IsCrouching
if Input.is_action_just_pressed("Jump") and is_on_floor():
AlreadyJ = false
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var input_dir := Input.get_vector("Left", "Right", "Backwards", "Forward")
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
if direction:
IsMoving = true
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
IsMoving = false
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
animHandle()
extends Node2D
#GLOBAL SCRIPT (also working)
#UNUSED
var handMove = 0
var hurtTimer : float = 0.0 # Use floats for time
var throw = false
var angle = 0
var throw_direction : Vector3 = Vector3.ZERO
var throwcooldown : float = 0.0
var chunkscale = 0
var angleDIR = 1
#USED
var MousePos = Vector2(0,0)
var MouseVel = Vector2(0,0)
var HandR_LIVE = true
var HandL_LIVE = true
var HandR_INUSE = false
var HandL_INUSE = false
func _process(delta: float) -> void:
MousePos = get_global_mouse_position()
# unused
#hurtTimer -= delta
#throwcooldown -= delta
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
MouseVel = Vector2(event.relative.x, -event.relative.y)
TREE:
WHEN IDLE:
WHEN MOVING:
ROTATION TOWARDS CURSOR WHEN LEFT AND RIGHT CLICK HELD:
I would provide a video for better understanding but i am unable to as i am new to forums (i cannot upload any attachments as of right now).
If anyone is confused in any way, ask away. I will try to be as helpful as possible. :]




