player.gd 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. class_name Player extends CharacterBody3D
  2. enum _Anim {
  3. FLOOR,
  4. AIR,
  5. }
  6. const SHOOT_TIME = 1.5
  7. const SHOOT_SCALE = 2.0
  8. const CHAR_SCALE = Vector3(0.3, 0.3, 0.3)
  9. const MAX_SPEED = 6.0
  10. const TURN_SPEED = 40.0
  11. const JUMP_VELOCITY = 12.5
  12. const BULLET_SPEED = 20.0
  13. const AIR_IDLE_DEACCEL = false
  14. const ACCEL = 14.0
  15. const DEACCEL = 14.0
  16. const AIR_ACCEL_FACTOR = 0.5
  17. const SHARP_TURN_THRESHOLD = deg_to_rad(140.0)
  18. var movement_dir := Vector3()
  19. var jumping := false
  20. var prev_shoot := false
  21. var shoot_blend := 0.0
  22. # Number of coins collected.
  23. var coins := 0
  24. @onready var initial_position := position
  25. @onready var gravity: Vector3 = ProjectSettings.get_setting("physics/3d/default_gravity") * \
  26. ProjectSettings.get_setting("physics/3d/default_gravity_vector")
  27. @onready var _camera := $Target/Camera3D as Camera3D
  28. @onready var _animation_tree := $AnimationTree as AnimationTree
  29. func _physics_process(delta):
  30. if Input.is_action_pressed("reset_position") or global_position.y < -12:
  31. # Player hit the reset button or fell off the map.
  32. position = initial_position
  33. velocity = Vector3.ZERO
  34. # Update coin count and its "parallax" copies.
  35. # This gives text a pseudo-3D appearance while still using Label3D instead of the more limited TextMesh.
  36. %CoinCount.text = str(coins)
  37. %CoinCount.get_node("Parallax").text = str(coins)
  38. %CoinCount.get_node("Parallax2").text = str(coins)
  39. %CoinCount.get_node("Parallax3").text = str(coins)
  40. %CoinCount.get_node("Parallax4").text = str(coins)
  41. velocity += gravity * delta
  42. var anim := _Anim.FLOOR
  43. var vertical_velocity := velocity.y
  44. var horizontal_velocity := Vector3(velocity.x, 0, velocity.z)
  45. var horizontal_direction := horizontal_velocity.normalized()
  46. var horizontal_speed := horizontal_velocity.length()
  47. # Player input.
  48. var cam_basis := _camera.get_global_transform().basis
  49. var movement_vec2 := Input.get_vector(&"move_left", &"move_right", &"move_forward", &"move_back")
  50. var movement_direction := cam_basis * Vector3(movement_vec2.x, 0, movement_vec2.y)
  51. movement_direction.y = 0
  52. movement_direction = movement_direction.normalized()
  53. var jump_attempt := Input.is_action_pressed(&"jump")
  54. var shoot_attempt := Input.is_action_pressed(&"shoot")
  55. if is_on_floor():
  56. var sharp_turn := horizontal_speed > 0.1 and \
  57. acos(movement_direction.dot(horizontal_direction)) > SHARP_TURN_THRESHOLD
  58. if movement_direction.length() > 0.1 and not sharp_turn:
  59. if horizontal_speed > 0.001:
  60. horizontal_direction = adjust_facing(
  61. horizontal_direction,
  62. movement_direction,
  63. delta,
  64. 1.0 / horizontal_speed * TURN_SPEED,
  65. Vector3.UP
  66. )
  67. else:
  68. horizontal_direction = movement_direction
  69. if horizontal_speed < MAX_SPEED:
  70. horizontal_speed += ACCEL * delta
  71. else:
  72. horizontal_speed -= DEACCEL * delta
  73. if horizontal_speed < 0:
  74. horizontal_speed = 0
  75. horizontal_velocity = horizontal_direction * horizontal_speed
  76. var mesh_xform := ($Player/Skeleton as Node3D).get_transform()
  77. var facing_mesh := -mesh_xform.basis[0].normalized()
  78. facing_mesh = (facing_mesh - Vector3.UP * facing_mesh.dot(Vector3.UP)).normalized()
  79. if horizontal_speed > 0:
  80. facing_mesh = adjust_facing(
  81. facing_mesh,
  82. movement_direction,
  83. delta,
  84. 1.0 / horizontal_speed * TURN_SPEED,
  85. Vector3.UP
  86. )
  87. var m3 := Basis(
  88. -facing_mesh,
  89. Vector3.UP,
  90. -facing_mesh.cross(Vector3.UP).normalized()
  91. ).scaled(CHAR_SCALE)
  92. $Player/Skeleton.set_transform(Transform3D(m3, mesh_xform.origin))
  93. if not jumping and jump_attempt:
  94. vertical_velocity = JUMP_VELOCITY
  95. jumping = true
  96. $SoundJump.play()
  97. else:
  98. anim = _Anim.AIR
  99. if movement_direction.length() > 0.1:
  100. horizontal_velocity += movement_direction * (ACCEL * AIR_ACCEL_FACTOR * delta)
  101. if horizontal_velocity.length() > MAX_SPEED:
  102. horizontal_velocity = horizontal_velocity.normalized() * MAX_SPEED
  103. elif AIR_IDLE_DEACCEL:
  104. horizontal_speed = horizontal_speed - (DEACCEL * AIR_ACCEL_FACTOR * delta)
  105. if horizontal_speed < 0:
  106. horizontal_speed = 0
  107. horizontal_velocity = horizontal_direction * horizontal_speed
  108. if Input.is_action_just_released("jump") and velocity.y > 0.0:
  109. # Reduce jump height if releasing the jump key before reaching the apex.
  110. vertical_velocity *= 0.7
  111. if jumping and vertical_velocity < 0:
  112. jumping = false
  113. velocity = horizontal_velocity + Vector3.UP * vertical_velocity
  114. if is_on_floor():
  115. movement_dir = velocity
  116. move_and_slide()
  117. if shoot_blend > 0:
  118. shoot_blend *= 0.97
  119. if (shoot_blend < 0):
  120. shoot_blend = 0
  121. if shoot_attempt and not prev_shoot:
  122. shoot_blend = SHOOT_TIME
  123. var bullet := preload("res://player/bullet/bullet.tscn").instantiate() as Bullet
  124. bullet.set_transform($Player/Skeleton/Bullet.get_global_transform().orthonormalized())
  125. get_parent().add_child(bullet)
  126. bullet.set_linear_velocity(
  127. $Player/Skeleton/Bullet.get_global_transform().basis[2].normalized() * BULLET_SPEED
  128. )
  129. bullet.add_collision_exception_with(self)
  130. $SoundShoot.play()
  131. prev_shoot = shoot_attempt
  132. if is_on_floor():
  133. # How much the player should be blending between the "idle" and "walk/run" animations.
  134. _animation_tree[&"parameters/run/blend_amount"] = horizontal_speed / MAX_SPEED
  135. # How much the player should be running (as opposed to walking). 0.0 = fully walking, 1.0 = fully running.
  136. _animation_tree[&"parameters/speed/blend_amount"] = minf(1.0, horizontal_speed / (MAX_SPEED * 0.5))
  137. _animation_tree[&"parameters/state/blend_amount"] = anim
  138. _animation_tree[&"parameters/air_dir/blend_amount"] = clampf(-velocity.y / 4 + 0.5, 0, 1)
  139. _animation_tree[&"parameters/gun/blend_amount"] = minf(shoot_blend, 1.0)
  140. func adjust_facing(facing: Vector3, target: Vector3, step: float, adjust_rate: float, \
  141. current_gn: Vector3) -> Vector3:
  142. var normal := target
  143. var t := normal.cross(current_gn).normalized()
  144. var x := normal.dot(facing)
  145. var y := t.dot(facing)
  146. var ang := atan2(y,x)
  147. if absf(ang) < 0.001:
  148. return facing
  149. var s := signf(ang)
  150. ang = ang * s
  151. var turn := ang * adjust_rate * step
  152. var a: float
  153. if ang < turn:
  154. a = ang
  155. else:
  156. a = turn
  157. ang = (ang - a) * s
  158. return (normal * cos(ang) + t * sin(ang)) * facing.length()