- 完整代码
https://github.com/CheapMiao/Godot-ThirdPersonController
extends KinematicBody
export var moveSpeed : float = 10
export var jumpAcceleration : float = 200
export var fallAcceleration : float = 9.8
var linearVelocity : Vector3 = Vector3.ZERO
export var mouseSensitivity : float = 0.05
export var mouseMoveMaxSpeed : float = 10
export var cameraMinPitch : float = -45
export var cameraMaxPitch : float = 90
export var playerRotSpeed : float = 0.2
export var slipAcceleration : float = 1
onready var meshes = $Meshes
onready var springarm = $SpringArm
onready var camera = $SpringArm/Camera
var shouldCameraMove : bool = false
var mouseMoveSpeed = Vector2(0,0)
var yAcceleration = 0
var yAccelerationScale : float = 10
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _unhandled_input(event) -> void:
if event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
shouldCameraMove = true
mouseMoveSpeed = event.relative
if Input.is_action_just_released("ui_cancel"):
print("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 playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
linearVelocity = direction * moveSpeed
if is_on_floor():
if Input.is_action_pressed("jump"):
yAcceleration = jumpAcceleration
else:
yAcceleration = slipAcceleration
else:
yAcceleration -= fallAcceleration
linearVelocity += Vector3.UP * yAcceleration / yAccelerationScale
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
func meshesRotate(deltaTime):
var meshesForwardVector = meshes.get_global_transform().basis.z
var springarmForwardVector = -springarm.get_global_transform().basis.z
var angle = meshesForwardVector.angle_to(springarmForwardVector)
var deltaVector = springarmForwardVector - meshesForwardVector
if deltaVector.dot(meshes.get_global_transform().basis.x) < 0:
angle = -angle
angle *= playerRotSpeed
meshes.rotate_y(angle)
func _physics_process(deltaTime):
playerMove(deltaTime)
cameraRotate(deltaTime)
meshesRotate(deltaTime)
布局:
- Debug 过程
一开始做的弹簧臂旋转
onready var springArm = $SpringArm
func _unhandled_input(event):
if event is InputEventMouseMotion:
var mouseMoveLocalDir = event.speed.normalized()
var mouseMoveArcLength = event.speed.length()
var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
var playerWorldForwardDir = get_global_transform().basis.z
var springArmRotAxis = mouseMoveWorldDir.cross(playerWorldForwardDir)
var mouseMoveAngle = mouseMoveArcLength/clamp(springArm.get_hit_length(),1,springArm.spring_length)
springArm.global_rotate(springArmRotAxis,mouseMoveAngle)
运行起来一团糟,然后我觉得可能是需要放到 _physics_process 中,所以改成了
var mouseSensitivity = 0.01
onready var springarm = $SpringArm
var mouseMoveLocalDir = Vector2(0,0)
var mouseMoveArcLength = 0
func _unhandled_input(event):
if event is InputEventMouseMotion:
mouseMoveLocalDir = event.speed.normalized()
mouseMoveArcLength = event.speed.length()
else:
mouseMoveLocalDir = Vector2(0,0)
mouseMoveArcLength = 0
func springarmRotate(deltaTime):
var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
var playerWorldForwardDir = get_global_transform().basis.z
var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)
func _physics_process(deltaTime):
springarmRotate(deltaTime)
print(mouseMoveArcLength)
运行起来,旋转方向是正确的,但是会出现频闪,还有鼠标静止时仍在旋转的问题。 这一看就是 _unhandled_input 中的 InputEventMouseMotion 对鼠标移动速度的获取出了问题。理想情况下,应该是鼠标移动时,_unhandled_input 识别到 InputEventMouseMotion,获得鼠标速度;鼠标不移动时,_unhandled_input 识别不到 InputEventMouseMotion,不获得鼠标速度
测试 _unhandled_input 获得 event 的机制
var tmp = null
func _unhandled_input(event):
tmp = event
func _physics_process(deltaTime):
print(tmp)
结果是,只要把鼠标在窗口中滑动一下,然后鼠标不动,打印出来的 event 就只是最后一次 InputEventMouseMotion,也就是说,只有有 input 的时候,_unhandled_input 才工作 官方文档 http://godot.pro/doc/tutorials/inputs/inputevent.html 中解释了 inputevent 的传递流,它的意思就是,有 inputevnet,_unhandled_input 才可能工作。 综合: ① 有 input 的时候,_unhandled_input 才工作 ② 有 inputevnet,_unhandled_input 才可能工作 可见,没有 input 就没有 inputevent 鼠标静止的时候,就是没有 input 的时候,所以鼠标静止不会给出一个 inputevent 放到 _unhandled_input 中,所以我的使用 _unhandled_input 获得鼠标移动速度的逻辑有问题,只能获取到最后一次记录的鼠标移动的速度。 ……好吧,虽然这是可以理解的
之后又搜了一下,看到别人也问过这个问题 https://stackoverflow.com/questions/62844337/godot-how-would-i-get-inputeventmousemotion-in-the-process-function,现在可以知道,我写的这个获取鼠标速度的错误逻辑就相当于 Input.get_last_mouse_speed() 因此我又把旋转脚本改为
var mouseSensitivity = 0.01
onready var springarm = $SpringArm
func springarmRotate(deltaTime):
var mouseMoveLocalDir = Vector2(0,0)
var mouseMoveArcLength = 0
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()
var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
var playerWorldForwardDir = get_global_transform().basis.z
var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)
func _physics_process(deltaTime):
springarmRotate(deltaTime)
运行结果是根本不旋转,根本没进入 if Input.get_current_cursor_shape() == Input.CURSOR_MOVE
于是我在 _physics_process 中试了一下 print(Input.get_current_cursor_shape()),发现我不管怎么移动鼠标或者点击鼠标按键,它一直打印的是 0,即 CURSOR_ARROW 箭头模式……我也知道是箭头模式啊……思考……不知道他这个设计是为了什么,好反直觉 再看它的英文含义,有没有可能是我用错了?get_current_cursor_shape 和 get_mouse_mode,一个是 获得鼠标形状 一个是 获得鼠标状态,都一直获得 0,也就是说,一直是 MOUSE_MODE_VISIBLE 和 CURSOR_ARROW 虽然确实没问题,但是我是因为看到 CursorShape 中有 CURSOR_MOVE 才想着用 get_current_cursor_shape 的……简直无敌
https://github.com/khairul169/3rdperson-godot/issues 这位写了一个第三人称的控制器,但是这个项目已经运行不了了 我点开它控制角色的脚本来看,第一时间没看懂,有点乱,也没注释,干脆就不看了
我再搜到的一个是 https://github.com/KevinStirling/ThirdPersonCameraGodot,他这个是写得真的简洁,功能也很正确
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:
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 :
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:
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))
原来它用的是 _unhandled_input 的 event.relative 我试着在 _unhandled_input 中打印 event.relative,发现他居然是我理想中的鼠标速度 emmmm……给我整不会了,我看文档的时候,它说
The mouse position relative to the previous position (position at the last frame).
我就没想到他这个”相对最后一帧的位置“可以用来表示速度 真的是学到了 然后,鼠标不动的时候 relative 也会一直返回到 0 那我前面说的:
可见,没有 input 就没有 inputevent 鼠标静止的时候,就是没有 input 的时候,所以鼠标静止不会给出一个 inputevent 放到 _unhandled_input 中,所以我的使用 _unhandled_input 获得鼠标移动速度的逻辑有问题,只能获取到最后一次记录的鼠标移动的速度。
就是错的了……
但是这样的话就有点矛盾了。最大的可能是,其实没有 input 的时候,也会有 inputevent 传入 _unhandled_input,只是鼠标静止的时候,speed 为 null,所以打印不出来,给我造成了没有 event 的错觉
这位 KevinStirling 老哥有他自己的一套计算逻辑,我现在暂时先按照我的计算逻辑来的话,就是
extends KinematicBody
export var moveSpeed = 10
export var fallAcceleration = 75
var linearVelocity = Vector3.ZERO
var mouseSensitivity = 0.1
onready var springarm = $SpringArm
var mouseMoveLocalDir = Vector2(0,0)
var mouseMoveArcLength = 0
func _unhandled_input(event) -> void:
if event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
mouseMoveLocalDir = event.relative
mouseMoveArcLength = mouseMoveLocalDir.length()
func playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
$MeshInstance.look_at(translation + direction, Vector3.UP)
linearVelocity.x = direction.x * moveSpeed
linearVelocity.z = direction.z * moveSpeed
linearVelocity.y -= fallAcceleration * deltaTime
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func springarmRotate(deltaTime):
var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,-mouseMoveLocalDir.y,0))
var playerWorldForwardDir = get_global_transform().basis.z
var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)
func _physics_process(deltaTime):
playerMove(deltaTime)
springarmRotate(deltaTime)
结果还是出现了那个
可见,没有 input 就没有 inputevent 鼠标静止的时候,就是没有 input 的时候,所以鼠标静止不会给出一个 inputevent 放到 _unhandled_input 中,所以我的使用 _unhandled_input 获得鼠标移动速度的逻辑有问题,只能获取到最后一次记录的鼠标移动的速度。
的问题 好奇怪啊,那就是我对自己的否定又是错的了——我原来想的没错? 那别人是怎么做到流畅的旋转的?好吧,别人仅仅是把移动逻辑写到了 _unhandled_input 里面而已 好吧,原来别人就是通过 _unhandled_input 的”鼠标静止的时候,就是没有 input 的时候“的特性,实现鼠标静止的时候,不调用 _unhandled_input,其中的旋转函数不调用,就不旋转,即”鼠标静止就不旋转“的效果的 草……为什么我会纠结这么久
为了统一移动逻辑到 _physics_process 中,我再改成:
extends KinematicBody
export var moveSpeed : float = 10
export var jumpVelocity : float = 30
export var fallAcceleration : float = 100
var linearVelocity : Vector3 = Vector3.ZERO
export var mouseSensitivity : float = 1
onready var springarm = $SpringArm
var shouldSpringArmMove : bool = false
var mouseMoveLocalDir = Vector2(0,0)
var mouseMoveArcLength = 0
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func get_input() -> void:
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 _unhandled_input(event) -> void:
if event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
shouldSpringArmMove = true
mouseMoveLocalDir = event.relative
mouseMoveArcLength = mouseMoveLocalDir.length()
func playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
$MeshInstance.look_at(translation + direction, Vector3.UP)
linearVelocity = direction * moveSpeed
if Input.is_action_pressed("jump"):
linearVelocity += Vector3.UP * jumpVelocity
linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func springarmRotate(deltaTime):
if shouldSpringArmMove:
shouldSpringArmMove = false
var mouseMoveWorldDir = to_global(Vector3(-mouseMoveLocalDir.x,mouseMoveLocalDir.y,0))
var playerWorldForwardDir = get_global_transform().basis.z
var springarmRotAxis = (mouseMoveWorldDir.cross(playerWorldForwardDir)).normalized()
var mouseMoveAngle = mouseMoveArcLength/clamp(springarm.get_hit_length(),1,springarm.spring_length)
springarm.global_rotate(springarmRotAxis,mouseMoveAngle*deltaTime*mouseSensitivity)
func _physics_process(deltaTime):
playerMove(deltaTime)
springarmRotate(deltaTime)
这下旋转是大概没有问题了,但是又出现了一个闪烁的现象
主要原因是,我对弹簧臂的旋转包括了绕 x 轴的旋转。弹簧臂很容易被旋转到水平面之下,撞到地面上,然后收缩,然后直接进入 player 的内部,这样就看不到 player 了;接着再转动一下弹簧臂,假设转动后弹簧臂又回到水平面之上,又回到原来的长度,就又可以看到 player 了,这样一来一回就导致了频闪。
正确的做法应该是,对摄像机绕 x 轴旋转,对弹簧臂绕 y 轴旋转
这么说的话,还是要像他那样子单独 rotate 了 直接用 event.relative.x/10 和 event.relative.y/10 作为 lerp 的参数用于旋转有点不太好,毕竟鼠标移动得快一点就可以让 event.relative 的 x 和 y 大于 10 了,但是他这个是无所谓呃,因为它是用 mouseSensitivity 把每一帧旋转的速度卡死的。这样,如果鼠标移动地很慢,摄像机转动也慢;如果鼠标转动很快,在一定范围内,鼠标移动越快,旋转速度越快,但是当鼠标移动速度大于某一值时,旋转速度不再增加。这就可以防止有人用力过猛导致的混乱,也给出了一个又慢又快的体感。
修改之后:
extends KinematicBody
export var moveSpeed : float = 10
export var jumpVelocity : float = 30
export var fallAcceleration : float = 100
var linearVelocity : Vector3 = Vector3.ZERO
export var mouseSensitivity : float = 0.05
export var mouseMoveMaxSpeed : float = 10
onready var springarm = $SpringArm
onready var camera = $SpringArm/Camera
var shouldCameraMove : bool = false
var mouseMoveSpeed = Vector2(0,0)
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func get_input() -> void:
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 _unhandled_input(event) -> void:
if event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
shouldCameraMove = true
mouseMoveSpeed = event.relative
func playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
$MeshInstance.look_at(translation + direction, Vector3.UP)
linearVelocity = direction * moveSpeed
if Input.is_action_pressed("jump"):
linearVelocity += Vector3.UP * jumpVelocity
linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
func _physics_process(deltaTime):
playerMove(deltaTime)
cameraRotate(deltaTime)
运行起来还是又闪烁……这就说明我认为是弹簧臂的原因是不正确的
这仍然是摄像机旋转的问题,旋转角度应该还是存在着一些不连续,导致渲染出了问题
我看到一篇教程 https://godottutorials.pro/third-person-controller-tutorial/,他是把摄像机旋转放到了 _process 中 于是我把我的代码改成
func _physics_process(deltaTime):
playerMove(deltaTime)
func _process(deltaTime):
cameraRotate(deltaTime)
还是会有闪烁的问题……
继续根据教程,我在摄像机旋转的函数中补上对鼠标速度变量的清零
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
mouseMoveSpeed = Vector2.ZERO
还是会有闪烁的问题……
此外这个教程就没有什么东西了
这样的话,其实我和这些流畅的控制器的唯一区别就是,我希望让弹簧臂绕 y 轴旋转,但是他们都是直接将整个角色绕 y 轴旋转。有可能是这个区别导致了我出现闪烁的现象。或者说,弹簧臂绕 y 轴旋转导致了闪烁
测试1 只有摄像机绕 x 轴旋转:
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
运行结果
摄像机绕 x 轴旋转正常
测试2 只有弹簧臂绕 y 轴旋转
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
运行结果
确实是弹簧臂绕 y 轴旋转导致了闪烁
但是我并不想让整个角色绕 y 轴旋转……这就陷入了僵局 后面我随便乱调的时候,我觉得视野有点不好,就把弹簧臂调高了
然后就神奇地没有闪烁了! 我不知道为什么……
问题暂时解决,于是继续往下写
extends KinematicBody
export var moveSpeed : float = 10
export var jumpVelocity : float = 30
export var fallAcceleration : float = 100
var linearVelocity : Vector3 = Vector3.ZERO
export var mouseSensitivity : float = 0.05
export var mouseMoveMaxSpeed : float = 10
export var cameraMinPitch : float = -45
export var cameraMaxPitch : float = 90
export var playerRotSpeed : float = 0.5
onready var meshes = $Meshes
onready var springarm = $SpringArm
onready var camera = $SpringArm/Camera
var shouldCameraMove : bool = false
var mouseMoveSpeed = Vector2(0,0)
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func get_input() -> void:
if Input.is_action_pressed("ui_cancel"):
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 event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
shouldCameraMove = true
mouseMoveSpeed = event.relative
func playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
linearVelocity = direction * moveSpeed
if Input.is_action_pressed("jump"):
linearVelocity += Vector3.UP * jumpVelocity
linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
func meshesRotate(deltaTime):
meshes.rotation_degrees.y = lerp(meshes.rotation_degrees.y, springarm.rotation_degrees.y, playerRotSpeed)
func _physics_process(deltaTime):
playerMove(deltaTime)
cameraRotate(deltaTime)
meshesRotate(deltaTime)
我想让 Mesh 的旋转角跟随弹簧臂的旋转角,但是在某个角度上,Mesh 的旋转角有突变
测试:
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)
运行结果:
弹簧臂的 y 方向的旋转角存在一个从 -180 到 180 的突变
这样的话,就不能直接用弹簧臂的 y 方向的旋转角了 只能算旋转增量了
更换旋转方法:
extends KinematicBody
export var moveSpeed : float = 10
export var jumpVelocity : float = 30
export var fallAcceleration : float = 100
var linearVelocity : Vector3 = Vector3.ZERO
export var mouseSensitivity : float = 0.05
export var mouseMoveMaxSpeed : float = 10
export var cameraMinPitch : float = -45
export var cameraMaxPitch : float = 90
export var playerRotSpeed : float = 0.2
onready var meshes = $Meshes
onready var springarm = $SpringArm
onready var camera = $SpringArm/Camera
var shouldCameraMove : bool = false
var mouseMoveSpeed = Vector2(0,0)
func _ready():
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func get_input() -> void:
if Input.is_action_pressed("ui_cancel"):
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 event is InputEventMouseMotion:
if typeof(event.relative) == TYPE_VECTOR2:
shouldCameraMove = true
mouseMoveSpeed = event.relative
func playerMove(deltaTime):
var direction = Vector3.ZERO
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
if direction != Vector3.ZERO:
direction = direction.normalized()
linearVelocity = direction * moveSpeed
if Input.is_action_pressed("jump"):
linearVelocity += Vector3.UP * jumpVelocity
linearVelocity -= Vector3.UP * fallAcceleration * deltaTime
linearVelocity = move_and_slide(linearVelocity, Vector3.UP)
func cameraRotate(deltaTime):
if shouldCameraMove:
shouldCameraMove = false
camera.rotate_x(-lerp(0, mouseSensitivity, mouseMoveSpeed.y/mouseMoveMaxSpeed))
camera.rotation_degrees.x = clamp(camera.rotation_degrees.x,cameraMinPitch,cameraMaxPitch)
springarm.rotate_y(-lerp(0, mouseSensitivity, mouseMoveSpeed.x/mouseMoveMaxSpeed))
func meshesRotate(deltaTime):
var meshesForwardVector = meshes.get_global_transform().basis.z
var springarmForwardVector = -springarm.get_global_transform().basis.z
var angle = meshesForwardVector.angle_to(springarmForwardVector)
var deltaVector = springarmForwardVector - meshesForwardVector
if deltaVector.dot(meshes.get_global_transform().basis.x) < 0:
angle = -angle
angle *= playerRotSpeed
meshes.rotate_y(angle)
func _physics_process(deltaTime):
playerMove(deltaTime)
cameraRotate(deltaTime)
meshesRotate(deltaTime)
运行结果:
旋转确实是已经做好了 但是还是有一点小问题,就是感觉跳跃太快了,因为他这个跳跃是直接给一个速度 但是 KinematicBody 还真就没有物理模拟的函数,所以还是要自己实现一个
实现结果:
终于起码是可以看得过眼了
http://godot.pro/doc/tutorials/inputs/inputevent.html 官方文档 inputevent https://stackoverflow.com/questions/48438273/godot-3d-get-forward-vector 获得物体的前方向 https://stackoverflow.com/questions/62844337/godot-how-would-i-get-inputeventmousemotion-in-the-process-function 获取 inputeventmousemotion 的方法 https://github.com/khairul169/3rdperson-godot/issues 一个老的第三人称工程 https://github.com/KevinStirling/ThirdPersonCameraGodot 一个最近的第三人称工程 https://godottutorials.pro/third-person-controller-tutorial/ 制作第三人称控制器的图文教程 https://www.youtube.com/watch?v=SIGnJLtgk7w&ab_channel=Zenva 制作第三人称控制器的视频教程
|