character_controller.script 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. -- First-person 3D camera controller
  2. -- Tuning parameters
  3. local look_sensitivity = 0.15 -- degrees of camera rotation per 1 pixel of mouse movement
  4. local move_speed = 0.5 -- world units per second for camera movement on XZ plane
  5. local move_limit = 1.25 -- bounds (half-size) for camera movement on XZ to keep it in a square area
  6. function init(self)
  7. -- Acquire input focus to receive input events from the engine
  8. msg.post(".", "acquire_input_focus")
  9. -- Mouse lock state: when true, mouse deltas rotate the camera
  10. self.mouse_locked = false
  11. -- Initialize yaw/pitch from current rotation (stored in degrees in Defold)
  12. self.yaw = go.get(".", "euler.y")
  13. self.pitch = go.get(".", "euler.x")
  14. -- Input state for continuous movement (WASD)
  15. self.input = {
  16. forward = false,
  17. backward = false,
  18. left = false,
  19. right = false,
  20. }
  21. end
  22. function update(self, dt)
  23. -- Clamp pitch to avoid flipping the camera upside down
  24. if self.pitch > 89 then self.pitch = 89 end
  25. if self.pitch < -89 then self.pitch = -89 end
  26. -- Apply rotation directly via Euler angles (in degrees)
  27. go.set(".", "euler", vmath.vector3(self.pitch, self.yaw, 0))
  28. -- Build desired movement direction on XZ plane from input flags
  29. local x = (self.input.right and 1 or 0) - (self.input.left and 1 or 0)
  30. local z = (self.input.backward and 1 or 0) - (self.input.forward and 1 or 0)
  31. -- If there is any movement input, move the camera
  32. if x ~= 0 or z ~= 0 then
  33. -- Local space direction (camera space)
  34. local local_dir = vmath.vector3(x, 0, z)
  35. local len = math.sqrt(local_dir.x * local_dir.x + local_dir.z * local_dir.z)
  36. if len > 0 then
  37. -- Normalize to keep speed consistent diagonally
  38. local_dir.x = local_dir.x / len
  39. local_dir.z = local_dir.z / len
  40. -- Convert the yaw to a quaternion
  41. local q_yaw = vmath.quat_rotation_y(math.rad(self.yaw))
  42. -- Convert local movement to world space using current yaw
  43. local world_dir = vmath.rotate(q_yaw, local_dir)
  44. -- Get the current position of the character
  45. local pos = go.get_position()
  46. -- Integrate the position
  47. pos.x = pos.x + world_dir.x * move_speed * dt
  48. pos.z = pos.z + world_dir.z * move_speed * dt
  49. -- Clamp the position within the square bounds
  50. if pos.x > move_limit then pos.x = move_limit end
  51. if pos.x < -move_limit then pos.x = -move_limit end
  52. if pos.z > move_limit then pos.z = move_limit end
  53. if pos.z < -move_limit then pos.z = -move_limit end
  54. -- Set the new position
  55. go.set_position(pos)
  56. end
  57. end
  58. end
  59. -- Pre-hashed input action ids (must match project input bindings)
  60. local KEY_W = hash("key_w")
  61. local KEY_S = hash("key_s")
  62. local KEY_A = hash("key_a")
  63. local KEY_D = hash("key_d")
  64. local KEY_ESC = hash("key_esc")
  65. local TOUCH = hash("touch")
  66. local MOUSE_BUTTON_1 = hash("mouse_button_1")
  67. function on_input(self, action_id, action)
  68. -- Mouse look when locked: engine provides action.dx/dy even while cursor is locked
  69. if self.mouse_locked and (action.dx or action.dy) then
  70. -- Rotate the camera based on the mouse movement
  71. self.yaw = self.yaw - (action.dx or 0) * look_sensitivity
  72. self.pitch = self.pitch + (action.dy or 0) * look_sensitivity
  73. end
  74. -- Lock on first click (touch or left mouse button)
  75. if not self.mouse_locked and action.pressed
  76. and (action_id == TOUCH or action_id == MOUSE_BUTTON_1) then
  77. -- Lock the mouse
  78. window.set_mouse_lock(true)
  79. self.mouse_locked = true
  80. end
  81. -- WSAD - Continuous movement input state (pressed/released)
  82. if action_id == KEY_W then
  83. -- Set the forward input flag to true if the W key is pressed
  84. if action.pressed then self.input.forward = true end
  85. if action.released then self.input.forward = false end
  86. end
  87. if action_id == KEY_S then
  88. -- Set the backward input flag to true if the S key is pressed
  89. if action.pressed then self.input.backward = true end
  90. if action.released then self.input.backward = false end
  91. end
  92. if action_id == KEY_A then
  93. -- Set the left input flag to true if the A key is pressed
  94. if action.pressed then self.input.left = true end
  95. if action.released then self.input.left = false end
  96. end
  97. if action_id == KEY_D then
  98. -- Set the right input flag to true if the D key is pressed
  99. if action.pressed then self.input.right = true end
  100. if action.released then self.input.right = false end
  101. end
  102. -- ESC unlocks the mouse so the cursor is free again
  103. if action_id == KEY_ESC and action.pressed then
  104. -- Unlock the mouse
  105. window.set_mouse_lock(false)
  106. self.mouse_locked = false
  107. end
  108. end