Created
October 5, 2025 11:16
-
-
Save nklbdev/ec5b39420aa68e40ac5106660b49e587 to your computer and use it in GitHub Desktop.
Blockbench WASD controls plugin for v5 with RMB and mouse lock
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| const deletables = []; | |
| let rightMouseDown = false; | |
| BBPlugin.register("wasd_controls", { | |
| title: "WASD Controls", | |
| icon: "sports_esports", | |
| author: "JannisX11, caioraphael1, nklbdev", | |
| description: "Adds a WASD controlled viewport navigation mode", | |
| about: | |
| "The WASD mode can be enabled from the View menu.\nThe keys can be remapped in the keybindings menu.\nThe sensitivity can be changed in the settings under Preview, along with the movement plane.\nHold Control to move faster.", | |
| version: "1.2.0", | |
| min_version: "4.3.0", | |
| variant: "both", | |
| onload() { | |
| const navigate_forward = new KeybindItem("navigate_forward", { | |
| name: "Move Forward", | |
| icon: "arrow_upward", | |
| category: "navigate", | |
| keybind: new Keybind({ key: "w", ctrl: null }), | |
| }); | |
| const navigate_backward = new KeybindItem("navigate_backward", { | |
| name: "Move Backward", | |
| icon: "arrow_downward", | |
| category: "navigate", | |
| keybind: new Keybind({ key: "s", ctrl: null }), | |
| }); | |
| const navigate_left = new KeybindItem("navigate_left", { | |
| name: "Move Left", | |
| icon: "arrow_back", | |
| category: "navigate", | |
| keybind: new Keybind({ key: "a", ctrl: null }), | |
| }); | |
| const navigate_right = new KeybindItem("navigate_right", { | |
| name: "Move Right", | |
| icon: "arrow_forward", | |
| category: "navigate", | |
| keybind: new Keybind({ key: "d", ctrl: null }), | |
| }); | |
| const navigate_down = new KeybindItem("navigate_down", { | |
| name: "Move Down", | |
| icon: "expand_more", | |
| category: "navigate", | |
| keybind: new Keybind({ key: 16, ctrl: null }), | |
| }); | |
| const navigate_up = new KeybindItem("navigate_up", { | |
| name: "Move Up", | |
| icon: "expand_less", | |
| category: "navigate", | |
| keybind: new Keybind({ key: 32, ctrl: null }), | |
| }); | |
| const navigate_faster = new KeybindItem("navigate_faster", { | |
| name: "Move Faster", | |
| icon: "expand_less", | |
| category: "navigate", | |
| keybind: new Keybind({ key: 17, ctrl: null }), | |
| }); | |
| const navigate_slower = new KeybindItem("navigate_slower", { | |
| name: "Move Slower", | |
| icon: "expand_less", | |
| category: "navigate", | |
| keybind: new Keybind({ key: 18, ctrl: null }), | |
| }); | |
| const navigation_keybinds = [ | |
| navigate_forward, | |
| navigate_backward, | |
| navigate_left, | |
| navigate_right, | |
| navigate_down, | |
| navigate_up, | |
| navigate_faster, | |
| navigate_slower, | |
| ]; | |
| deletables.push(...navigation_keybinds); | |
| function setupWASDMovement(preview, length = 1) { | |
| const pos = new THREE.Vector3().copy(preview.camera.position); | |
| pos.add( | |
| preview.camera | |
| .getWorldDirection(new THREE.Vector3()) | |
| .normalize() | |
| .multiplyScalar(length), | |
| ); | |
| preview.controls.target.copy(pos); | |
| } | |
| deletables.push( | |
| new Setting("wasd_enabled", { | |
| name: "WASD Controls: Enabled", | |
| description: "_", | |
| category: "preview", | |
| value: false, | |
| onChange(value) { | |
| BarItems.wasd_movement.value = value; | |
| BarItems.wasd_movement.updateEnabledState(); | |
| setupWASDMovement(Preview.selected, value ? 1 : 16); | |
| }, | |
| }), | |
| ); | |
| const wasd_toggle = new Toggle("wasd_movement", { | |
| name: "WASD Movement", | |
| icon: "sports_esports", | |
| category: "navigate", | |
| value: false, | |
| onChange(value) { | |
| settings.wasd_enabled.value = value; | |
| Settings.saveLocalStorages(); | |
| setupWASDMovement(Preview.selected, value ? 1 : 16); | |
| }, | |
| }); | |
| deletables.push(wasd_toggle); | |
| MenuBar.menus.view.addAction("_"); | |
| MenuBar.menus.view.addAction(wasd_toggle); | |
| BarItems.wasd_movement.value = settings.wasd_enabled.value; | |
| BarItems.wasd_movement.updateEnabledState(); | |
| function isWASDMovementEnabled() { | |
| return ( | |
| rightMouseDown && Preview.selected && BarItems.wasd_movement?.value | |
| ); | |
| } | |
| deletables.push( | |
| new Setting("base_speed", { | |
| name: "WASD Controls: Base Speed", | |
| description: "-", | |
| category: "preview", | |
| type: "number", | |
| value: 50, | |
| min: 1, | |
| }), | |
| ); | |
| deletables.push( | |
| new Setting("move_faster_mult", { | |
| name: "WASD Controls: Move Faster Multiplier", | |
| description: "-", | |
| category: "preview", | |
| type: "number", | |
| value: 2, | |
| max: 10, | |
| min: 1, | |
| }), | |
| ); | |
| deletables.push( | |
| new Setting("move_slower_mult", { | |
| name: "WASD Controls: Move Slower Multiplier", | |
| description: "-", | |
| category: "preview", | |
| type: "number", | |
| value: 0.5, | |
| max: 1, | |
| min: 0.1, | |
| }), | |
| ); | |
| deletables.push( | |
| new Setting("wasd_y_level", { | |
| name: "WASD Controls: Navigate at Y Level", | |
| description: | |
| "Navigate using WASD at consistent Y level rather than on camera plane", | |
| category: "preview", | |
| value: true, | |
| }), | |
| ); | |
| deletables.push( | |
| new Setting("mouse_sensitivity", { | |
| name: "WASD Controls: Mouse Sensitivity", | |
| description: "-", | |
| category: "preview", | |
| type: "number", | |
| value: 0.005, | |
| max: 0.01, | |
| min: 0.001, | |
| }), | |
| ); | |
| // const maxPitch = 85 * (Math.PI / 180); | |
| deletables.push( | |
| new Setting("max_camera_pitch_degrees", { | |
| name: "WASD Controls: Max Camera Pitch Angle (degrees)", | |
| description: "-", | |
| category: "preview", | |
| type: "number", | |
| value: 85, | |
| max: 89, | |
| min: 45, | |
| }), | |
| ); | |
| deletables.push( | |
| new Setting("invert_pitch", { | |
| name: "WASD Controls: Invert Pitch", | |
| description: "-", | |
| category: "preview", | |
| value: true, | |
| }), | |
| ); | |
| const pressed_keys = []; | |
| Blockbench.on("press_key", (data) => { | |
| // TODO: Заменить which на использование актуального свойства | |
| const key = data.event.which; | |
| const is_keybind_found = navigation_keybinds.find( | |
| (k) => k.keybind.key === key, | |
| ); | |
| const is_any_text_input_focused = Boolean(getFocusedTextInput()); | |
| if (is_keybind_found && !is_any_text_input_focused) { | |
| pressed_keys.safePush(key); | |
| if (isWASDMovementEnabled()) data.capture(); | |
| } | |
| }); | |
| document.addEventListener("keyup", (event) => { | |
| // TODO: Заменить which на использование актуального свойства | |
| pressed_keys.remove(event.which); | |
| }); | |
| // event.button - индекс изменившей состояние кнопки | |
| // { LEFT = 0, MIDDLE = 1, RIGHT = 2 } | |
| // event.buttons - флаги нажатых в данный момент кнопок (не совпадают с индексами) | |
| // { LEFT = 0001b (1), RIGHT = 0010b (2), MIDDLE = 0100b (4) } | |
| function rightMouseDownUpdater(event) { | |
| const oldRightMouseDown = rightMouseDown; | |
| rightMouseDown = event.buttons & 2; | |
| if (oldRightMouseDown !== rightMouseDown) { | |
| if (rightMouseDown) document.body.requestPointerLock(); | |
| else document.exitPointerLock(); | |
| } | |
| } | |
| document.addEventListener("mousedown", rightMouseDownUpdater); | |
| document.addEventListener("mouseup", rightMouseDownUpdater); | |
| const vector_zero = new THREE.Vector3(); | |
| const targetDistance = 1.0; // Фиксированное расстояние до target | |
| const temp_quaternion = new THREE.Quaternion(); | |
| const cameraForward = new THREE.Vector3(); | |
| const cameraRight = new THREE.Vector3(); | |
| document.addEventListener("mousemove", (event) => { | |
| if (isWASDMovementEnabled()) { | |
| const { camera, controls } = Preview.selected; | |
| // Получаем текущее направление взгляда | |
| cameraForward | |
| .subVectors(controls.target, camera.position) | |
| .normalize(); | |
| cameraRight | |
| .crossVectors(camera.up, camera.getWorldDirection(vector_zero)) | |
| .normalize(); | |
| // Применяем вращения к направлению | |
| const sensitivity = settings.mouse_sensitivity.value; | |
| cameraForward.applyQuaternion( | |
| temp_quaternion.setFromAxisAngle( | |
| camera.up, | |
| -event.movementX * sensitivity, | |
| ), | |
| ); | |
| // Создаем кватернионы для вращения | |
| cameraForward.applyQuaternion( | |
| temp_quaternion.setFromAxisAngle( | |
| cameraRight, | |
| -event.movementY * | |
| sensitivity * | |
| (settings.invert_pitch.value ? -1 : 1), | |
| ), | |
| ); | |
| // Вычисляем результирующий угол pitch и ограничиваем его | |
| const resultPitch = Math.asin(cameraForward.y); | |
| const maxPitch = | |
| settings.max_camera_pitch_degrees.value * (Math.PI / 180); | |
| // TODO: исправить ограничение налона | |
| if (Math.abs(resultPitch) > maxPitch) { | |
| // Просто ограничиваем Y-компоненту, сохраняя горизонтальное направление | |
| cameraForward.y = Math.sign(resultPitch) * Math.sin(maxPitch); | |
| cameraForward.normalize(); | |
| } | |
| // Обновляем позицию target | |
| controls.target.copy(camera.position); | |
| controls.target.add(cameraForward.multiplyScalar(targetDistance)); | |
| // Заставляем камеру смотреть на новый target | |
| camera.lookAt(controls.target); | |
| controls.update(); | |
| } | |
| }); | |
| function doWASDMovement() { | |
| const movement = new THREE.Vector3(0, 0, 0); | |
| let uses_wasd_movement = false; | |
| function add(x, y, z) { | |
| movement.x += x; | |
| movement.y += y; | |
| movement.z += z; | |
| uses_wasd_movement = true; | |
| } | |
| if (pressed_keys.includes(navigate_forward.keybind.key)) add(0, 0, -1); | |
| if (pressed_keys.includes(navigate_backward.keybind.key)) add(0, 0, 1); | |
| if (pressed_keys.includes(navigate_left.keybind.key)) add(-1, 0, 0); | |
| if (pressed_keys.includes(navigate_right.keybind.key)) add(1, 0, 0); | |
| if (pressed_keys.includes(navigate_down.keybind.key)) add(0, -1, 0); | |
| if (pressed_keys.includes(navigate_up.keybind.key)) add(0, 1, 0); | |
| if (uses_wasd_movement) { | |
| setupWASDMovement(Preview.selected); | |
| let speedMultiplier = 1.0; // Default speed | |
| if (pressed_keys.includes(navigate_faster.keybind.key)) | |
| speedMultiplier *= settings.move_faster_mult.value; | |
| else if (pressed_keys.includes(navigate_slower.keybind.key)) | |
| speedMultiplier *= settings.move_slower_mult.value; | |
| if (settings.wasd_y_level.value) { | |
| const vec = Preview.selected.controls.object | |
| .getWorldDirection(new THREE.Vector3()) | |
| .normalize(); | |
| movement.applyAxisAngle(THREE.NormalY, Math.atan2(-vec.x, -vec.z)); | |
| } else { | |
| movement.applyEuler(Preview.selected.controls.object.rotation); | |
| } | |
| movement.multiplyScalar( | |
| (Settings.get("base_speed") * speedMultiplier) / 100, | |
| ); | |
| Preview.selected.camera.position.add(movement); | |
| Preview.selected.controls.target.add(movement); | |
| } | |
| } | |
| Blockbench.on("render_frame", () => { | |
| if (isWASDMovementEnabled() && pressed_keys.length) doWASDMovement(); | |
| // Ensure zoom is disabled when moving | |
| for (const preview of Preview.all) | |
| preview.controls.enableZoom = !isWASDMovementEnabled(); | |
| }); | |
| }, | |
| oninstall() { | |
| MenuBar.menus.view.highlight(BarItems.wasd_movement); | |
| }, | |
| onunload() { | |
| for (const action of deletables) action.delete(); | |
| setupWASDMovement(Preview.selected, 16); | |
| for (const preview of Preview.all) preview.controls.enableZoom = true; | |
| window.animate = animate_original; | |
| }, | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment