| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- -- First-person 3D camera controller
- -- Tuning parameters
- local look_sensitivity = 0.15 -- degrees of camera rotation per 1 pixel of mouse movement
- local move_speed = 0.5 -- world units per second for camera movement on XZ plane
- local move_limit = 1.25 -- bounds (half-size) for camera movement on XZ to keep it in a square area
- function init(self)
- -- Acquire input focus to receive input events from the engine
- msg.post(".", "acquire_input_focus")
- -- Mouse lock state: when true, mouse deltas rotate the camera
- self.mouse_locked = false
- -- Initialize yaw/pitch from current rotation (stored in degrees in Defold)
- self.yaw = go.get(".", "euler.y")
- self.pitch = go.get(".", "euler.x")
- -- Input state for continuous movement (WASD)
- self.input = {
- forward = false,
- backward = false,
- left = false,
- right = false,
- }
- end
- function update(self, dt)
- -- Clamp pitch to avoid flipping the camera upside down
- if self.pitch > 89 then self.pitch = 89 end
- if self.pitch < -89 then self.pitch = -89 end
- -- Apply rotation directly via Euler angles (in degrees)
- go.set(".", "euler", vmath.vector3(self.pitch, self.yaw, 0))
- -- Build desired movement direction on XZ plane from input flags
- local x = (self.input.right and 1 or 0) - (self.input.left and 1 or 0)
- local z = (self.input.backward and 1 or 0) - (self.input.forward and 1 or 0)
- -- If there is any movement input, move the camera
- if x ~= 0 or z ~= 0 then
- -- Local space direction (camera space)
- local local_dir = vmath.vector3(x, 0, z)
- local len = math.sqrt(local_dir.x * local_dir.x + local_dir.z * local_dir.z)
- if len > 0 then
- -- Normalize to keep speed consistent diagonally
- local_dir.x = local_dir.x / len
- local_dir.z = local_dir.z / len
- -- Convert the yaw to a quaternion
- local q_yaw = vmath.quat_rotation_y(math.rad(self.yaw))
- -- Convert local movement to world space using current yaw
- local world_dir = vmath.rotate(q_yaw, local_dir)
- -- Get the current position of the character
- local pos = go.get_position()
- -- Integrate the position
- pos.x = pos.x + world_dir.x * move_speed * dt
- pos.z = pos.z + world_dir.z * move_speed * dt
- -- Clamp the position within the square bounds
- if pos.x > move_limit then pos.x = move_limit end
- if pos.x < -move_limit then pos.x = -move_limit end
- if pos.z > move_limit then pos.z = move_limit end
- if pos.z < -move_limit then pos.z = -move_limit end
- -- Set the new position
- go.set_position(pos)
- end
- end
- end
- -- Pre-hashed input action ids (must match project input bindings)
- local KEY_W = hash("key_w")
- local KEY_S = hash("key_s")
- local KEY_A = hash("key_a")
- local KEY_D = hash("key_d")
- local KEY_ESC = hash("key_esc")
- local TOUCH = hash("touch")
- local MOUSE_BUTTON_1 = hash("mouse_button_1")
- function on_input(self, action_id, action)
- -- Mouse look when locked: engine provides action.dx/dy even while cursor is locked
- if self.mouse_locked and (action.dx or action.dy) then
- -- Rotate the camera based on the mouse movement
- self.yaw = self.yaw - (action.dx or 0) * look_sensitivity
- self.pitch = self.pitch + (action.dy or 0) * look_sensitivity
- end
- -- Lock on first click (touch or left mouse button)
- if not self.mouse_locked and action.pressed
- and (action_id == TOUCH or action_id == MOUSE_BUTTON_1) then
- -- Lock the mouse
- window.set_mouse_lock(true)
- self.mouse_locked = true
- end
- -- WSAD - Continuous movement input state (pressed/released)
- if action_id == KEY_W then
- -- Set the forward input flag to true if the W key is pressed
- if action.pressed then self.input.forward = true end
- if action.released then self.input.forward = false end
- end
- if action_id == KEY_S then
- -- Set the backward input flag to true if the S key is pressed
- if action.pressed then self.input.backward = true end
- if action.released then self.input.backward = false end
- end
- if action_id == KEY_A then
- -- Set the left input flag to true if the A key is pressed
- if action.pressed then self.input.left = true end
- if action.released then self.input.left = false end
- end
- if action_id == KEY_D then
- -- Set the right input flag to true if the D key is pressed
- if action.pressed then self.input.right = true end
- if action.released then self.input.right = false end
- end
- -- ESC unlocks the mouse so the cursor is free again
- if action_id == KEY_ESC and action.pressed then
- -- Unlock the mouse
- window.set_mouse_lock(false)
- self.mouse_locked = false
- end
- end
|