I came across this thread while looking for info on how to use Tween.follow_property(). Like what is discussed above, I also thought that follow_property() would automatically detect changes in the target property. However, I'm convinced that unfortunately it's just a minor variation of interpolate_property().
The two functions signatures are very similar, the only difference is that in interpolate_property you give a specific end value, while in follow_property you instead specify an object and its property.
So, the following two code samples would be interchangeable:
var start_value = node.rotation_degrees
var end_value = target.rotation_degrees
tween.interpolate_property(node, "rotation_degrees", start_value, end_value, 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
var start_value = node.rotation_degrees
tween.follow_property(node, "rotation_degrees", start_value, target, "rotation_degrees", 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
As already discussed, the follow function isn't as smart as its name suggests, and you have to set up a mechanism for detecting the changes of its target. However, this might not be so much boilerplate required as one would thought. Just identify where the changes are actually happening, that is, where and what in the code is indirecting causing the changes, and put signals on that node/object.
For example, in my 3D game I want the rotation of an arm of a character to follow the rotation of the character's neck. So when a character is looking in a certain direction, the raised arm should rotate towards that direction.
The solution became simpler than I would have thought. I put scripts on the Arm and the Neck. The main part of the code is coded in the Arm script. The arm can be raised and lowered. When raised, I connect to the Neck's signal, when lowered I disconnect.
The script on the arm:
extends Spatial
const READY_SPEED = 0.75 # (seconds)
const FOLLOW_SPEED = 0.15 # (seconds)
onready var tween = $Tween
onready var neck = get_node("../Neck")
var ready = [false, false] # left, right raised and ready
func follow_neck_rotation(node):
var start_degrees = node.rotation_degrees
var end_degrees = neck.rotation_degrees + Vector3(90, 0, 0)
tween.interpolate_property(node, "rotation_degrees", start_degrees, end_degrees, FOLLOW_SPEED, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
pass
func raise_arm(node):
# initial ready sequence
var start_degrees = node.rotation_degrees
var end_degrees = neck.rotation_degrees + Vector3(90, 0, 0)
tween.interpolate_property(node, "rotation_degrees", start_degrees, end_degrees, READY_SPEED, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
# connect to neck rotation
neck.connect("looked_left", self, "_on_Neck_looked_left")
neck.connect("looked_up", self, "_on_Neck_looked_up")
pass
func lower_arm(node):
# disconnect neck rotation
neck.disconnect("looked_left", self, "_on_Neck_looked_left")
neck.disconnect("looked_up", self, "_on_Neck_looked_up")
# final unready sequence
var start_degrees = node.rotation_degrees
var end_degrees = Vector3(0, 0, 0)
tween.interpolate_property(node, "rotation_degrees", start_degrees, end_degrees, READY_SPEED, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start()
pass
func raise_left_arm():
if not ready[0]:
raise_arm($Left)
ready[0] = true
else:
lower_arm($Left)
ready[0] = false
pass
func raise_right_arm():
if not ready[1]:
raise_arm($Right)
ready[1] = true
else:
lower_arm($Right)
ready[1] = false
pass
func _on_Neck_looked_up():
var node = $Left if ready[0] else $Right
follow_neck_rotation(node)
pass
func _on_Neck_looked_left():
var node = $Left if ready[0] else $Right
follow_neck_rotation(node)
pass
I actually can't use follow_property() because I need to add an additional 90 degrees to the arm. So I ended up using only interpolate_property().
The script on the neck:
extends Spatial
signal looked_up
signal looked_left
const LOOKAT_MAX_ANGLE_X = 60 # rotation up/down
const LOOKAT_MAX_ANGLE_Y = 75 # rotation left/right
func look_up(angle):
# rotate neck up/down
rotation_degrees.x -= angle
if rotation_degrees.x > LOOKAT_MAX_ANGLE_X:
rotation_degrees.x = LOOKAT_MAX_ANGLE_X
if rotation_degrees.x < 0 - LOOKAT_MAX_ANGLE_X:
rotation_degrees.x = 0 - LOOKAT_MAX_ANGLE_X
emit_signal("looked_up")
pass
func look_left(angle):
# rotate neck left/right
rotation_degrees.y -= angle
if rotation_degrees.y > LOOKAT_MAX_ANGLE_Y:
rotation_degrees.y = LOOKAT_MAX_ANGLE_Y
if rotation_degrees.y < 0 - LOOKAT_MAX_ANGLE_Y:
rotation_degrees.y = 0 - LOOKAT_MAX_ANGLE_Y
emit_signal("looked_left")
pass
So the signals are put in the neck's functions where the rotation is indirectly changed. It's not needed to try to somehow catch the changes in rotation_degrees directly.
The neck is then rotated in the player input code.
In Player.gd:
func _input(event):
# look at
if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
if player_camera.current:
if event is InputEventMouseMotion:
# rotate neck up/down
if event.relative.y:
character.neck.look_up(event.relative.y * LOOKAT_SPEED)
if event.relative.x:
character.neck.look_left(event.relative.x * LOOKAT_SPEED)
For NPC characters I will implement neck rotations somewhere in their AI code, but I havn't come to that yet.
Here's a short YouTube video showing how it finally looked like. My video recording skills are not the very best, but I hope it shows how the raised arm is synced with the head movements.
https://youtu.be/4I6IE1gDajI
This thread is two years old so I'm not sure how much my experience with this matter helped. But I thought it could be worthwhile to post this comment still, in case someone else runs into issues when trying to get properties follow each other.