[Godot] [gdscript] third person 3C implementation of thirdpersoncontroller

  1. Complete code

https://github.com/CheapMiao/Godot-ThirdPersonController

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed : float = 10
# Jump speed
export var jumpAcceleration : float = 200
# Falling acceleration
export var fallAcceleration : float = 9.8
# Linear velocity
var linearVelocity : Vector3 = Vector3.ZERO
# Mouse sensitivity
export var mouseSensitivity : float = 0.05
# Maximum mouse movement speed
export var mouseMoveMaxSpeed : float = 10
# Minimum pitch angle
export var cameraMinPitch : float = -45
# Maximum pitch angle
export var cameraMaxPitch : float = 90
# Character turning speed
export var playerRotSpeed : float = 0.2
# Acceleration of a character sliding on a slope
export var slipAcceleration : float = 1

# ---Component reference---

# Mesh
onready var meshes = $Meshes
# Spring arm
onready var springarm = $SpringArm
# video camera
onready var camera = $SpringArm/Camera

# ---Control cache---

# Should the spring arm be rotated
var shouldCameraMove : bool = false
# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveSpeed = Vector2(0,0)
# Acceleration in y direction
var yAcceleration = 0

# ---Control parameters---

# Scaling of acceleration in y direction
# In order to keep fallAcceleration unchanged in 9.8, it is common sense
var yAccelerationScale : float = 10

# ---Events---

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

func _unhandled_input(event) -> void:
	
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# The camera should be rotated
			shouldCameraMove = true
			# Gets the amount of mouse movement within a frame
			mouseMoveSpeed = event.relative
	
	# If you press the exit key
	if Input.is_action_just_released("ui_cancel"):
		print("cancel")
		# Toggle between mouse hide and dock
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# ---Horizontal direction---
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Obtain the front, rear, left and right directions of the camera
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction += camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_left"):
		direction -= camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_up"):
		direction -= camera.get_global_transform().basis.z
	if Input.is_action_pressed("move_down"):
		direction += camera.get_global_transform().basis.z
			
	# Horizontal movement direction unitization
	if direction != Vector3.ZERO:
		direction = direction.normalized()
	
	# Horizontal linear velocity
	linearVelocity = direction * moveSpeed
	
	# ---Vertical direction---
	
	# On the ground, judge whether to jump
	if is_on_floor():
		# Take off on the ground, jump acceleration
		if Input.is_action_pressed("jump"):
			yAcceleration = jumpAcceleration
		# If there is no take-off on the ground, the downward acceleration is the slope sliding acceleration
		else:
			yAcceleration = slipAcceleration
	# Not on the ground, gravitational acceleration
	else:
		yAcceleration -= fallAcceleration
	
	# Apply y-direction acceleration
	linearVelocity += Vector3.UP * yAcceleration / yAccelerationScale
	# Character movement
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Clamp
		camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))

# Player model rotation
func meshesRotate(deltaTime):
	# meshes front direction
	var meshesForwardVector = meshes.get_global_transform().basis.z
	# The front direction of the spring arm is opposite to the expected front direction due to the setting of the spring arm
	var springarmForwardVector = -springarm.get_global_transform().basis.z
	# Angle between meshes front direction and spring arm front direction
	var angle = meshesForwardVector.angle_to(springarmForwardVector)
	# Vector from mesh front direction to spring arm front direction
	var deltaVector = springarmForwardVector - meshesForwardVector
	
	# rotate_ The direction in which x increases is counterclockwise
	# If the direction from the mesh front to the spring arm front is clockwise, set the angle to negative
	if deltaVector.dot(meshes.get_global_transform().basis.x) < 0:
		angle = -angle
	
	# Apply character turn speed
	angle *= playerRotSpeed
	
	# meshes rotation
	meshes.rotate_y(angle)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	cameraRotate(deltaTime)
	meshesRotate(deltaTime)

Layout:

  1. Debug process

Spring arm rotation at the beginning

# Spring arm
onready var springArm = $SpringArm

func _unhandled_input(event):

	if event is InputEventMouseMotion:
		# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
		var mouseMoveLocalDir = event.speed.normalized()
		# The mouse movement speed on the two-dimensional plane of the object coordinate system is regarded as the arc length on the ball with the player as the ball center and the spring arm as the radius
		var mouseMoveArcLength = event.speed.length()
		# Mouse movement direction in 3D space of world coordinate system
		var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
		# Player's front direction in three-dimensional space of world coordinate system
		var playerWorldForwardDir = get_global_transform().basis.z
		# The rotation axis of the spring arm in the three-dimensional space of the world coordinate system is the cross product of the moving direction of the mouse and the front direction of the player in the three-dimensional space
		var springArmRotAxis = mouseMoveWorldDir.cross(playerWorldForwardDir)
		# The rotation angle of the spring arm is equal to the arc length divided by the radius
		var mouseMoveAngle = mouseMoveArcLength/clamp(springArm.get_hit_length(),1,springArm.spring_length)
		# Spring arm rotation
		springArm.global_rotate(springArmRotAxis,mouseMoveAngle)
		

It runs in a mess, and then I think it may need to be put in_ physics_process, so it's changed to

# Mouse sensitivity
var mouseSensitivity = 0.01

# ---Component reference---

# Spring arm
onready var springarm = $SpringArm

# ---Control cache---

# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveLocalDir = Vector2(0,0)
# The movement speed of the mouse on the two-dimensional plane of the object coordinate system is regarded as the arc length on the ball with the player as the center and the spring arm as the radius
var mouseMoveArcLength = 0

# ---Input event---

# Get mouse motion status
func _unhandled_input(event):

	if event is InputEventMouseMotion:
		mouseMoveLocalDir = event.speed.normalized()
		mouseMoveArcLength = event.speed.length()
	else:
		mouseMoveLocalDir = Vector2(0,0)
		mouseMoveArcLength = 0

# ---Custom function---

# Spring arm rotation
func springarmRotate(deltaTime):
	# Mouse movement direction in 3D space of world coordinate system
	var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
	# Player's front direction in three-dimensional space of world coordinate system
	var playerWorldForwardDir = get_global_transform().basis.z
	# The rotation axis of the spring arm in the three-dimensional space of the world coordinate system is the cross product of the mouse movement direction and the player's front direction in the three-dimensional space, which needs to be unitized
	var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
	# The rotation angle of the spring arm is equal to the arc length divided by the radius
	var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
	
	# Physical time interval and mouse sensitivity shall be considered for spring arm rotation
	springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	springarmRotate(deltaTime)
	print(mouseMoveArcLength)
	

When running, the rotation direction is correct, but there will be stroboscopic and still rotating when the mouse is stationary.
At first glance_ unhandled_ InputEventMouseMotion in input has a problem with the speed of mouse movement. Ideally, when the mouse moves_ unhandled_input recognizes InputEventMouseMotion and obtains the mouse speed; When the mouse does not move_ unhandled_input does not recognize InputEventMouseMotion and does not obtain mouse speed

Testing_ unhandled_ Mechanism for obtaining event by input

# ---Input event---

var tmp = null

# Get mouse motion status
func _unhandled_input(event):
	
	tmp = event

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	print(tmp)
	

As a result, as long as you slide the mouse in the window, and then keep the mouse still, the printed event is only the last InputEventMouseMotion, that is, only when there is input_ unhandled_input only works
Official documents http://godot.pro/doc/tutorials/inputs/inputevent.html The delivery flow of inputevent is explained in, which means that there is inputevnet_ unhandled_input can work.
Comprehensive:
① When there is input_ unhandled_input only works
② inputevnet_ unhandled_input can work
It can be seen that without input, there is no inputevent
When the mouse is still, there is no input, so when the mouse is still, an inputevent will not be given_ unhandled_input, so I use_ unhandled_ There is a problem with the logic of obtaining the mouse movement speed by input. Only the last recorded mouse movement speed can be obtained.
... well, although this is understandable

Then I searched again and saw that others had asked this question https://stackoverflow.com/questions/62844337/godot-how-would-i-get-inputeventmousemotion-in-the-process-function Now you can see that the error logic I wrote to obtain the mouse speed is equivalent to input get_ last_ mouse_ speed()
So I changed the rotation script to

# ---Object properties---

# Mouse sensitivity
var mouseSensitivity = 0.01

# ---Component reference---

# Spring arm
onready var springarm = $SpringArm

# Spring arm rotation
func springarmRotate(deltaTime):
	
	# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
	var mouseMoveLocalDir = Vector2(0,0)
	# The mouse movement speed on the two-dimensional plane of the object coordinate system is regarded as the arc length on the ball with the player as the ball center and the spring arm as the radius
	var mouseMoveArcLength = 0
	
	# If the mouse is moving, the last recorded mouse movement speed is obtained
	if Input.get_current_cursor_shape() == Input.CURSOR_MOVE:
		print("mouse is moving")
		mouseMoveLocalDir = Input.get_last_mouse_speed().normalized()
		mouseMoveArcLength = Input.get_last_mouse_speed().length()
	
	# Mouse movement direction in 3D space of world coordinate system
	var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
	# Player's front direction in three-dimensional space of world coordinate system
	var playerWorldForwardDir = get_global_transform().basis.z
	# The rotation axis of the spring arm in the three-dimensional space of the world coordinate system is the cross product of the mouse movement direction and the player's front direction in the three-dimensional space, which needs to be unitized
	var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
	# The rotation angle of the spring arm is equal to the arc length divided by the radius
	var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
	
	# The physical time interval and mouse sensitivity should be considered when the spring arm rotates
	springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	springarmRotate(deltaTime)
	

The running result is that it does not rotate at all and does not enter if input at all get_ current_ cursor_ shape() == Input. CURSOR_ MOVE

So I was_ physics_ In process, I tried print(Input.get_current_cursor_shape()), and found that no matter how I move the mouse or click the mouse button, it always prints 0, that is, CURSOR_ARROW
Arrow pattern... I also know it's arrow pattern... Thinking... I don't know what his design is for. It's counter intuitive
Looking at its English meaning, is it possible that I used it wrong? get_current_cursor_shape and get_mouse_mode, one is to obtain the mouse shape, the other is to obtain the mouse state, and always get 0, that is, always move_ MODE_ Visible and CURSOR_ARROW
Although there is no problem, I see cursor in CursorShape_ Move just wanted to use get_current_cursor_shape's... It's invincible

https://github.com/khairul169/3rdperson-godot/issues This wrote a third person controller, but the project can't run anymore
I clicked on the script to control the role. I didn't understand it at the first time. It was a little messy and didn't comment. I didn't read it at all

The other one I found was https://github.com/KevinStirling/ThirdPersonCameraGodot , his writing is really concise and the function is also very correct

extends KinematicBody

export var gravity : int = -12
export var speed : int = 6
export var jump_speed : int = 6
export var air_speed : int = 4
export(float, 0.01, 1) var mouse_sens = 0.05
export(float, -90, 90) var min_camera_angle = -90
export(float, -90, 90) var max_camera_angle = 90

onready var camera : Spatial = $CameraOrbit

var velocity : Vector3 = Vector3()
var jump : bool = false

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


func get_input() -> void:
#	Handle input and set velocity accordingly
	var vy = velocity.y
	velocity = Vector3()
	var accel = speed if is_on_floor() else air_speed
	if Input.is_action_pressed("move_up"):
		velocity += -transform.basis.z * accel
	if Input.is_action_pressed("move_down"):
		velocity += transform.basis.z * accel
	if Input.is_action_pressed("move_right"):
		velocity += transform.basis.x * accel
	if Input.is_action_pressed("move_left"):
		velocity += -transform.basis.x * accel
	velocity = velocity.normalized() * speed
	velocity.y = vy
	jump = false
	if Input.is_action_just_pressed("jump"):
		jump = true
	if Input.is_action_just_pressed("ui_cancel"):
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)


func _physics_process(delta) -> void :
#	Apply velocity calculation to the physics process
	velocity.y += gravity * delta
	get_input()
	velocity = move_and_slide(velocity, Vector3.UP)
	if jump and is_on_floor():
		velocity.y = jump_speed

func _unhandled_input(event) -> void:
#	Translate mouse movement to camera and character model movement
	if event is InputEventMouseMotion:
		rotate_y(-lerp(0, mouse_sens, event.relative.x/10))
		camera.rotate_x(-lerp(0, mouse_sens, event.relative.y/10)) 
		camera.rotation.x = clamp(camera.rotation.x, deg2rad(min_camera_angle), deg2rad(max_camera_angle))

It turns out that it uses_ unhandled_ Event of input relative
I tried to_ unhandled_ Print event in input Relative, I found that he was actually my ideal mouse speed
emmmm... I can't fix it. When I read the document, it said

The mouse position relative to the previous position (position at the last frame).

I didn't think his "position relative to the last frame" could be used to express the speed
I really learned
Then, relative will always return to 0 when the mouse is not moving
What I said earlier:

It can be seen that without input, there is no inputevent
When the mouse is still, there is no input, so when the mouse is still, an inputevent will not be given_ unhandled_input, so I use_ unhandled_ There is a problem with the logic of obtaining the mouse movement speed by input. Only the last recorded mouse movement speed can be obtained.

It's wrong

But that's a little contradictory. The most likely thing is that when there is no input, there will also be inputevent_ unhandled_input, but when the mouse is still, the speed is null, so it can't be printed, which gives me the illusion that there is no event

Brother Kevin Stirling has his own set of calculation logic. I'll follow my calculation logic for the time being

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed = 10
# Falling acceleration
export var fallAcceleration = 75
# Linear velocity
var linearVelocity = Vector3.ZERO
# Mouse sensitivity
var mouseSensitivity = 0.1

# ---Component reference---

# Spring arm
onready var springarm = $SpringArm

# ---Control cache---

# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveLocalDir = Vector2(0,0)
# The mouse movement speed on the two-dimensional plane of the object coordinate system is regarded as the arc length on the ball with the player as the ball center and the spring arm as the radius
var mouseMoveArcLength = 0

# ---Events---

func _unhandled_input(event) -> void:
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# Gets the amount of mouse movement within a frame
			mouseMoveLocalDir = event.relative
			mouseMoveArcLength = mouseMoveLocalDir.length()

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Get the movement direction increment before, after, left and right
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction.x -= 1
	if Input.is_action_pressed("move_left"):
		direction.x += 1
	if Input.is_action_pressed("move_up"):
		direction.z += 1
	if Input.is_action_pressed("move_down"):
		direction.z -= 1
		
	# Unitization of moving direction
	# Mesh rotates in the direction of movement
	if direction != Vector3.ZERO:
		direction = direction.normalized()
		# If translation is the world coordinate of the parent node, then translation + direction is the point with length of 1 at the moving direction of the parent node
		$MeshInstance.look_at(translation + direction, Vector3.UP)
		
	# Horizontal linear velocity
	linearVelocity.x = direction.x * moveSpeed
	linearVelocity.z = direction.z * moveSpeed
	# Vertical velocity
	linearVelocity.y -= fallAcceleration * deltaTime
	# Moving the character
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Spring arm rotation
func springarmRotate(deltaTime):
	
	# Mouse movement direction in 3D space of world coordinate system
	var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
	# Player's front direction in three-dimensional space of world coordinate system
	var playerWorldForwardDir = get_global_transform().basis.z
	# The rotation axis of the spring arm in the three-dimensional space of the world coordinate system is the cross product of the mouse movement direction and the player's front direction in the three-dimensional space, which needs to be unitized
	var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
	# The rotation angle of the spring arm is equal to the arc length divided by the radius
	var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
	
	# The physical time interval and mouse sensitivity should be considered when the spring arm rotates
	springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	springarmRotate(deltaTime)

The result was that

It can be seen that without input, there is no inputevent
When the mouse is still, there is no input, so when the mouse is still, an inputevent will not be given_ unhandled_input, so I use_ unhandled_ There is a problem with the logic of obtaining the mouse movement speed by input. Only the last recorded mouse movement speed can be obtained.

Question of
Curious and strange, that is, my negation of myself is wrong again - what I thought was right?
How do others spin smoothly? Well, others just wrote the mobile logic_ unhandled_ It's just in input
Well, it turns out that others pass_ unhandled_ Input's feature of "when the mouse is still, there is no input". When the mouse is still, it does not call _unhandled_input, and the rotation function does not rotate if it is not called, that is, the effect of "when the mouse is still, it does not rotate"
Grass... Why did I tangle for so long

To unify mobile logic to_ physics_ In process, I change it to:

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed : float = 10
# Jump speed
export var jumpVelocity : float = 30
# Falling acceleration
export var fallAcceleration : float = 100
# Linear velocity
var linearVelocity : Vector3 = Vector3.ZERO
# Mouse sensitivity
export var mouseSensitivity : float = 1

# ---Component reference---

# Spring arm
onready var springarm = $SpringArm

# ---Control cache---

# Should the spring arm be rotated
var shouldSpringArmMove : bool = false
# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveLocalDir = Vector2(0,0)
# The mouse movement speed on the two-dimensional plane of the object coordinate system is regarded as the arc length on the ball with the player as the ball center and the spring arm as the radius
var mouseMoveArcLength = 0

# ---Events---

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

func get_input() -> void:
	# If you press the exit key
	if Input.is_action_just_pressed("ui_cancel"):
		# Toggle between mouse hide and dock
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			
func _unhandled_input(event) -> void:
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# The spring arm should be rotated
			shouldSpringArmMove = true
			# Gets the amount of mouse movement within a frame
			mouseMoveLocalDir = event.relative
			mouseMoveArcLength = mouseMoveLocalDir.length()

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# ---Horizontal direction---
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Get the horizontal movement direction increment before, after, left and right
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction.x -= 1
	if Input.is_action_pressed("move_left"):
		direction.x += 1
	if Input.is_action_pressed("move_up"):
		direction.z += 1
	if Input.is_action_pressed("move_down"):
		direction.z -= 1
			
	# Horizontal movement direction unitization
	# Mesh rotates horizontally
	if direction != Vector3.ZERO:
		direction = direction.normalized()
		# If translation is the world coordinate of the parent node, then translation + direction is the point with length 1 at the moving direction of the parent node
		$MeshInstance.look_at(translation + direction, Vector3.UP)
	
	# Horizontal linear velocity
	linearVelocity = direction * moveSpeed
	
	# ---Vertical direction---
	
	# Get vertical movement direction
	if Input.is_action_pressed("jump"):
		linearVelocity += Vector3.UP * jumpVelocity
	# Effect of applied gravity
	linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
	# Character movement
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Spring arm rotation
func springarmRotate(deltaTime):
	
	# If necessary, rotate the spring arm
	if shouldSpringArmMove:
		# The spring arm has started to rotate
		shouldSpringArmMove = false
		
		# Mouse movement direction in 3D space of world coordinate system
		var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,mouseMoveLocalDir.y,0))
		# Player's front direction in three-dimensional space of world coordinate system
		var playerWorldForwardDir = get_global_transform().basis.z
		# The rotation axis of the spring arm in the three-dimensional space of the world coordinate system is the cross product of the mouse movement direction and the player's front direction in the three-dimensional space, which needs to be unitized
		var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
		# The rotation angle of the spring arm is equal to the arc length divided by the radius
		var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)

		# The physical time interval and mouse sensitivity should be considered when the spring arm rotates
		springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	springarmRotate(deltaTime)

This rotation is probably no problem, but there is another flicker phenomenon

The main reason is that my rotation of the spring arm includes rotation about the x axis. The spring arm can easily be rotated below the horizontal plane, hit the ground, then contract, and then directly enter the interior of the player, so that the player can not be seen; Then turn the spring arm again. Suppose that after the rotation, the spring arm returns to the horizontal plane and returns to the original length, you can see the player again. In this way, it leads to stroboscopic.

The correct approach should be to rotate the camera around the x axis and the spring arm around the y axis

If you say so, you'll still have to rotate alone like him
Directly use event relative. x / 10 and event relative. Y / 10 as a parameter of lerp is not very good for rotation. After all, moving the mouse faster can make event Relative x and y are greater than 10, but it doesn't matter, er, because it uses mouseSensitivity to block the rotation speed of each frame. In this way, if the mouse moves very slowly, the camera rotates slowly; If the mouse rotates very fast, within a certain range, the faster the mouse moves, the faster the rotation speed. However, when the mouse moves faster than a certain value, the rotation speed will not increase. This can prevent confusion caused by excessive force and give a slow and fast body feeling.

After modification:

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed : float = 10
# Jump speed
export var jumpVelocity : float = 30
# Falling acceleration
export var fallAcceleration : float = 100
# Linear velocity
var linearVelocity : Vector3 = Vector3.ZERO
# Mouse sensitivity
export var mouseSensitivity : float = 0.05
# Maximum mouse movement speed
export var mouseMoveMaxSpeed : float = 10

# ---Component reference---

# Spring arm
onready var springarm = $SpringArm
# video camera
onready var camera = $SpringArm/Camera

# ---Control cache---

# Should the spring arm be rotated
var shouldCameraMove : bool = false
# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveSpeed = Vector2(0,0)

# ---Events---

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

func get_input() -> void:
	# If you press the exit key
	if Input.is_action_just_pressed("ui_cancel"):
		# Toggle between mouse hide and dock
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			
func _unhandled_input(event) -> void:
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# The camera should be rotated
			shouldCameraMove = true
			# Gets the amount of mouse movement within a frame
			mouseMoveSpeed = event.relative

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# ---Horizontal direction---
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Get the horizontal movement direction increment before, after, left and right
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction.x -= 1
	if Input.is_action_pressed("move_left"):
		direction.x += 1
	if Input.is_action_pressed("move_up"):
		direction.z += 1
	if Input.is_action_pressed("move_down"):
		direction.z -= 1
			
	# Horizontal movement direction unitization
	# Mesh rotates horizontally
	if direction != Vector3.ZERO:
		direction = direction.normalized()
		# If translation is the world coordinate of the parent node, then translation + direction is the point with length 1 at the moving direction of the parent node
		$MeshInstance.look_at(translation + direction, Vector3.UP)
	
	# Horizontal linear velocity
	linearVelocity = direction * moveSpeed
	
	# ---Vertical direction---
	
	# Get vertical movement direction
	if Input.is_action_pressed("jump"):
		linearVelocity += Vector3.UP * jumpVelocity
	# Effect of applied gravity
	linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
	# Character movement
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	cameraRotate(deltaTime)

It still flickers again... This shows that I think the reason for the spring arm is incorrect

This is still the problem of camera rotation. There should still be some discontinuities in the rotation angle, resulting in rendering problems

I saw a tutorial https://godottutorials.pro/third-person-controller-tutorial/ , he rotated the camera to_ In process
So I changed my code to

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)

# Actual frame rate execution
func _process(deltaTime):
	cameraRotate(deltaTime)
	

There will still be flickering problems

Continuing with the tutorial, I added the reset of the mouse speed variable in the camera rotation function

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
		# Reset camera speed
		mouseMoveSpeed = Vector2.ZERO
		

There will still be flickering problems

Besides, there is nothing in this tutorial

In this case, the only difference between me and these smooth controllers is that I want to rotate the spring arm around the Y axis, but they directly rotate the whole character around the Y axis. It is possible that this difference caused me to flicker. In other words, the rotation of the spring arm around the y-axis causes flicker

Test 1 only the camera rotates about the x axis:

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Rotating spring arm
		# springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))

Operation results

The camera rotates normally around the x axis

Test 2 only the spring arm rotates about the y-axis

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		# camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
		

Operation results

It is indeed the rotation of the spring arm around the y-axis that causes the flicker

But I don't want the whole character to rotate around the y axis... It's at an impasse
When I randomly adjusted in the back, I felt that the field of vision was a little bad, so I raised the spring arm

Then magically there was no flicker!
I don't know why

The problem was solved temporarily, so I continued to write

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed : float = 10
# Jump speed
export var jumpVelocity : float = 30
# Falling acceleration
export var fallAcceleration : float = 100
# Linear velocity
var linearVelocity : Vector3 = Vector3.ZERO
# Mouse sensitivity
export var mouseSensitivity : float = 0.05
# Maximum mouse movement speed
export var mouseMoveMaxSpeed : float = 10
# Minimum pitch angle
export var cameraMinPitch : float = -45
# Maximum pitch angle
export var cameraMaxPitch : float = 90
# Character turning speed
export var playerRotSpeed : float = 0.5

# ---Component reference---

# Mesh
onready var meshes = $Meshes
# Spring arm
onready var springarm = $SpringArm
# video camera
onready var camera = $SpringArm/Camera

# ---Control cache---

# Should the spring arm be rotated
var shouldCameraMove : bool = false
# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveSpeed = Vector2(0,0)

# ---Events---

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

func get_input() -> void:
	# If you press the exit key
	if Input.is_action_pressed("ui_cancel"):
		# Toggle between mouse hide and dock
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			print("Hi")
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			
func _unhandled_input(event) -> void:
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# The camera should be rotated
			shouldCameraMove = true
			# Gets the amount of mouse movement within a frame
			mouseMoveSpeed = event.relative

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# ---Horizontal direction---
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Obtain the front, rear, left and right directions of the camera
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction += camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_left"):
		direction -= camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_up"):
		direction -= camera.get_global_transform().basis.z
	if Input.is_action_pressed("move_down"):
		direction += camera.get_global_transform().basis.z
			
	# Horizontal movement direction unitization
	if direction != Vector3.ZERO:
		direction = direction.normalized()
	
	# Horizontal linear velocity
	linearVelocity = direction * moveSpeed
	
	# ---Vertical direction---
	
	# Get vertical movement direction
	if Input.is_action_pressed("jump"):
		linearVelocity += Vector3.UP * jumpVelocity
	# Effect of applied gravity
	linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
	# Character movement
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Clamp
		camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))

# Player rotation
func meshesRotate(deltaTime):
	meshes.rotation_degrees.y = lerp(meshes.rotation_degrees.y, springarm.rotation_degrees.y, playerRotSpeed)
	
# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	cameraRotate(deltaTime)
	meshesRotate(deltaTime)

I want the rotation angle of Mesh to follow the rotation angle of the spring arm, but there is a sudden change in the rotation angle of Mesh at a certain angle

Test:

# Player rotation
func meshesRotate(deltaTime):
	print("--------")
	print(meshes.rotation_degrees.y)
	print(springarm.rotation_degrees.y)
	meshes.rotation_degrees.y = lerp(meshes.rotation_degrees.y, springarm.rotation_degrees.y, playerRotSpeed)
	print(meshes.rotation_degrees.y)
	

Operation results:

There is a sudden change in the rotation angle of the spring arm in the y direction from - 180 to 180

In this case, the rotation angle in the y direction of the spring arm can not be used directly
It's only rotation increment

Replacement rotation method:

extends KinematicBody

# ---Object properties---

# Moving speed
export var moveSpeed : float = 10
# Jump speed
export var jumpVelocity : float = 30
# Falling acceleration
export var fallAcceleration : float = 100
# Linear velocity
var linearVelocity : Vector3 = Vector3.ZERO
# Mouse sensitivity
export var mouseSensitivity : float = 0.05
# Maximum mouse movement speed
export var mouseMoveMaxSpeed : float = 10
# Minimum pitch angle
export var cameraMinPitch : float = -45
# Maximum pitch angle
export var cameraMaxPitch : float = 90
# Character turning speed
export var playerRotSpeed : float = 0.2

# ---Component reference---

# Mesh
onready var meshes = $Meshes
# Spring arm
onready var springarm = $SpringArm
# video camera
onready var camera = $SpringArm/Camera

# ---Control cache---

# Should the spring arm be rotated
var shouldCameraMove : bool = false
# On the two-dimensional plane of the object coordinate system, the direction of mouse movement up to left is negative, down to right is positive
var mouseMoveSpeed = Vector2(0,0)

# ---Events---

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

func get_input() -> void:
	# If you press the exit key
	if Input.is_action_pressed("ui_cancel"):
		# Toggle between mouse hide and dock
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			print("Hi")
			Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		else:
			Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
			
func _unhandled_input(event) -> void:
	# If you get the mouse in motion event
	if event is InputEventMouseMotion:
		# If the displacement of the mouse relative to the last frame is not 0 but Vector2, it means that the mouse moves relative to the last frame
		if typeof(event.relative) == TYPE_VECTOR2:
			# The camera should be rotated
			shouldCameraMove = true
			# Gets the amount of mouse movement within a frame
			mouseMoveSpeed = event.relative

# ---Custom function---

# Player movement
func playerMove(deltaTime):
	
	# ---Horizontal direction---
	
	# Controls the direction of cache movement
	var direction = Vector3.ZERO
	
	# Obtain the front, rear, left and right directions of the camera
	# Notice the direction of the xyz coordinate system
	if Input.is_action_pressed("move_right"):
		direction += camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_left"):
		direction -= camera.get_global_transform().basis.x
	if Input.is_action_pressed("move_up"):
		direction -= camera.get_global_transform().basis.z
	if Input.is_action_pressed("move_down"):
		direction += camera.get_global_transform().basis.z
			
	# Horizontal movement direction unitization
	if direction != Vector3.ZERO:
		direction = direction.normalized()
	
	# Horizontal linear velocity
	linearVelocity = direction * moveSpeed
	
	# ---Vertical direction---
	
	# Get vertical movement direction
	if Input.is_action_pressed("jump"):
		linearVelocity += Vector3.UP * jumpVelocity
	# Effect of applied gravity
	linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
	# Character movement
	linearVelocity = move_and_slide(linearVelocity, Vector3.UP)

# Camera rotation
func cameraRotate(deltaTime):
	
	# Rotate the camera if necessary
	if shouldCameraMove:
		# Has started rotating the camera
		shouldCameraMove = false
		# Rotating camera
		camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
		# Clamp
		camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
		# Rotating spring arm
		springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))

# Player model rotation
func meshesRotate(deltaTime):
	# meshes front direction
	var meshesForwardVector = meshes.get_global_transform().basis.z
	# The front direction of the spring arm is opposite to the expected front direction due to the setting of the spring arm
	var springarmForwardVector = -springarm.get_global_transform().basis.z
	# Angle between meshes front direction and spring arm front direction
	var angle = meshesForwardVector.angle_to(springarmForwardVector)
	# Vector from mesh front direction to spring arm front direction
	var deltaVector = springarmForwardVector - meshesForwardVector
	
	# rotate_ The direction in which x increases is counterclockwise
	# If the direction from the mesh front to the spring arm front is clockwise, set the angle to negative
	if deltaVector.dot(meshes.get_global_transform().basis.x) < 0:
		angle = -angle
	
	# Apply character turn speed
	angle *= playerRotSpeed
	
	# meshes rotation
	meshes.rotate_y(angle)

# ---Virtual function implementation--- 

# Fixed frame rate execution
func _physics_process(deltaTime):
	
	playerMove(deltaTime)
	cameraRotate(deltaTime)
	meshesRotate(deltaTime)

Operation results:

The rotation is really done
But there is still a small problem, that is, he feels that the jump is too fast, because he gives a speed directly
However, the KinematicBody really doesn't have a function for physical simulation, so we still need to implement one by ourselves

Results achieved:

Finally, at least you can see it

http://godot.pro/doc/tutorials/inputs/inputevent.html
Official document inputevent
 
https://stackoverflow.com/questions/48438273/godot-3d-get-forward-vector
Get the front direction of the object
 
https://stackoverflow.com/questions/62844337/godot-how-would-i-get-inputeventmousemotion-in-the-process-function
Method for obtaining inputeventmousemotion
 
https://github.com/khairul169/3rdperson-godot/issues
An old third person project
 
https://github.com/KevinStirling/ThirdPersonCameraGodot
A recent third person project
 
https://godottutorials.pro/third-person-controller-tutorial/
Graphic tutorial for making third person controller
 
https://www.youtube.com/watch?v=SIGnJLtgk7w&ab_channel=Zenva
Video tutorial on making a third person controller

Keywords: Godot controller

Added by alex3 on Mon, 24 Jan 2022 14:03:16 +0200