"use strict"; const GameControls = (function(){ const GameControls = function(hollow_bond){ const self = this, euler = new THREE.Euler(0, 0, 0, "YXZ"), keys = { forward: false, backward: false, left: false, right: false, sprint: false, crouch: false, jump: false }; let is_pointer_locked = false; this.standing_height = 1.75; this.player_width = this.standing_height / 4; this.player_radius = this.player_width / 2; this.crouch_height = this.standing_height / 2; this.current_height = this.standing_height; this.walk_speed = 3.5; this.sprint_speed = 6.2; this.crouch_speed = 1.8; this.velocity_y = 0; this.is_grounded = false; this.is_crouching = false; this.set_dimensions = height => { self.standing_height = height || 1.75; self.player_width = self.standing_height / 4; self.player_radius = self.player_width / 2; self.crouch_height = self.standing_height / 2; self.current_height = self.standing_height; }; this.setup = dungeon => { const canvas = dungeon.renderer.domElement; canvas.addEventListener("click", () => canvas.requestPointerLock()); document.addEventListener("pointerlockchange", () => { is_pointer_locked = (document.pointerLockElement === canvas); }); document.addEventListener("mousemove", evt => { if(!is_pointer_locked) return; euler.setFromQuaternion(dungeon.camera.quaternion); euler.y -= evt.movementX * 0.002; euler.x -= evt.movementY * 0.002; euler.x = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, euler.x)); dungeon.camera.quaternion.setFromEuler(euler); }); window.addEventListener("keydown", evt => { switch(evt.code){ case "KeyW": case "ArrowUp": keys.forward = true; break; case "KeyS": case "ArrowDown": keys.backward = true; break; case "KeyA": case "ArrowLeft": keys.left = true; break; case "KeyD": case "ArrowRight": keys.right = true; break; case "ShiftLeft": case "ShiftRight": keys.sprint = true; break; case "KeyC": case "ControlLeft": keys.crouch = true; break; case "Space": keys.jump = true; break; } }); window.addEventListener("keyup", evt => { switch(evt.code){ case "KeyW": case "ArrowUp": keys.forward = false; break; case "KeyS": case "ArrowDown": keys.backward = false; break; case "KeyA": case "ArrowLeft": keys.left = false; break; case "KeyD": case "ArrowRight": keys.right = false; break; case "ShiftLeft": case "ShiftRight": keys.sprint = false; break; case "KeyC": case "ControlLeft": keys.crouch = false; break; case "Space": keys.jump = false; break; } }); }; this.update = (dungeon, delta) => { if(!is_pointer_locked) return; const gravity = hollow_bond.gravity || 9.81; // 1. Estado de agachado self.is_crouching = keys.crouch; const target_height = self.is_crouching ? self.crouch_height : self.standing_height; self.current_height += (target_height - self.current_height) * Math.min(1, 14 * delta); // 2. Velocidad de traslación let current_speed = self.walk_speed; if(self.is_crouching){ current_speed = self.crouch_speed; } else if(keys.sprint){ current_speed = self.sprint_speed; } // 3. Movimiento horizontal en XZ const input_dir = new THREE.Vector3(); if(keys.forward) input_dir.z -= 1; if(keys.backward) input_dir.z += 1; if(keys.left) input_dir.x -= 1; if(keys.right) input_dir.x += 1; if(input_dir.lengthSq() > 0){ input_dir.normalize(); const move_dir = new THREE.Vector3(input_dir.x, 0, input_dir.z); move_dir.applyAxisAngle(new THREE.Vector3(0, 1, 0), euler.y); dungeon.camera.position.x += move_dir.x * current_speed * delta; dungeon.camera.position.z += move_dir.z * current_speed * delta; } // 4. Salto if(self.is_grounded && keys.jump){ const jump_height = self.standing_height / 2; self.velocity_y = Math.sqrt(2 * gravity * jump_height); self.is_grounded = false; } // 5. Aplicar gravedad en el aire self.velocity_y -= gravity * delta; dungeon.camera.position.y += self.velocity_y * delta; // Reiniciamos apoyo; sólo un suelo bajo los pies lo activará self.is_grounded = false; // 6. Comprobación de apoyo en la sala actual if(dungeon.current_room && dungeon.current_room.collider){ dungeon.current_room.collider.resolve_player(dungeon.camera.position, self, self.player_radius); } // 7. Comprobación de marcos (solo de la sala actual y a la misma altura) if(dungeon.current_room && dungeon.current_room.model && dungeon.current_room.model.frames){ const feet_y = dungeon.camera.position.y - self.current_height; for(const fid of dungeon.current_room.model.frames){ const f_obj = dungeon.objects.frame ? dungeon.objects.frame[fid] : null; if(!f_obj || !f_obj.collider) continue; // Comprobamos XZ y verificamos que el marco esté en este mismo piso (+- 1 metro) const frame_y = f_obj.model ? f_obj.model.y : 0; if(Math.abs(feet_y - frame_y) < 1.0){ if(f_obj.collider.contains(dungeon.camera.position.x, dungeon.camera.position.z)){ f_obj.collider.resolve_player(dungeon.camera.position, self, self.player_radius); } } } } }; }; return GameControls; })();