top of page
Search

NPC Head Rotation Code

  • donovanpassantino
  • Mar 22
  • 2 min read

Updated: Apr 21

func rotate_head_to_target(delta : float, rotate_speed : float):
{
	if !m_rotate_head:
		return
	
	# Get temp var for target pos
	var target_pos : Vector3
	
	# The forward direction of the NPC
	var npc_forward = global_basis.z.normalized()
	
# The Transform of the bone in global space. 
Need to do global Transform * Bone Space
var bone_pose: Transform3D = m_skeleton.global_transform *
               m_skeleton.get_bone_global_pose(m_head_bone)
	# The rotation of the global bone 
	var bone_pose_rotation = bone_pose.basis.get_euler()
	# The look_at rotation of the NPC to target
	var head_rotation : Vector3
	
	# If there is a target do math to rotate head towards them
	if m_look_at_target:
{
		# Need to add UP vector because global pos is on ground
		target_pos = m_look_at_target.global_position + Vector3.UP
		# The distance between the npc and the target //Opitmized version
		var dist = global_position.distance_squared_to(target_pos)
		# The direction from the npc to the target
	var npc_dir_to_target = (target_pos - global_position).normalized()
		# The dot product between the NPC and the direction to the target
		var dot = npc_dir_to_target.dot(npc_forward)
		# The threshold of the view_width
		var threshold : float = cos(deg_to_rad(m_view_width))
		
		# (radius * radius) because dist is squared
		m_view_dist_squared = m_view_dist * m_view_dist
		
# Fov check if within view_width and if the target is within view_dist
var within_view : bool = dot > threshold and (dist <= m_view_dist_squared)
		
if m_debug:
    DebugDraw3D.draw_sphere(global_position, m_view_dist, Color.DARK_RED)
    draw_debug_dot_cone(npc_forward, m_view_dist, within_view)
		
if (within_view):
	# Get the look_at rotation from the NPC to target
	I think this is because of -Z forward
	head_rotation = bone_pose.looking_at(target_pos, Vector3.UP,
    true).basis.get_euler() + -bone_pose_rotation


}

# head rotation will be 0 deg if there is no target

# Convert rotation to degrees for clamping
head_rotation =
Vector3(rad_to_deg(head_rotation.x),rad_to_deg(head_rotation.y),rad_to_deg(head_rotation.z))
	
	# Clamp the angles based on specific values
	var head_rotation_degrees = head_rotation
	head_rotation_degrees.x = clamp(head_rotation_degrees.x, 
-m_vertical_angle, m_vertical_angle)
	
	# Angle lerp to get the fastest rotation 
	m_lerpValue.x = deg_to_rad(head_rotation_degrees.x)
	m_lerpValue.y = lerp_angle(m_lerpValue.y, deg_to_rad(head_rotation_degrees.y), (rotate_speed * delta))
	
	# Set the new rotation and set it to the rotation of the head bone
	var new_rotation = Quaternion.from_euler(Vector3(m_lerpValue.x, m_lerpValue.y, deg_to_rad(head_rotation_degrees.z)))
	m_skeleton.set_bone_pose_rotation(m_head_bone, new_rotation)
}



# Debug draw dot product cone
func draw_debug_dot_cone(dir : Vector3, radius : float, condition : bool):
{
	var color : Color = Color.DARK_RED
	
	if condition:
		color = Color.DARK_GREEN
	else:
		color = Color.DARK_RED
	
	var origin : Vector3 = (global_position + Vector3.UP)
	
	var forward_dir : Vector3 = dir * radius
	DebugDraw3D.draw_arrow(origin, origin + forward_dir, color, 0.1 )
	
	var left_dir : Vector3 = origin + forward_dir.rotated(Vector3.UP, deg_to_rad(m_view_width))
	DebugDraw3D.draw_arrow(origin, left_dir, color, 0.1 )
	
	var right_dir : Vector3 = origin + forward_dir.rotated(Vector3.UP, -deg_to_rad(m_view_width))
	DebugDraw3D.draw_arrow(origin, right_dir, color, 0.1 )
}


Note:

DebugDraw3D draw functions is a Free Godot Plugin


 
 
 

Comments


bottom of page