本文旨在解决JavaScript游戏中将键盘事件转换为触控事件时常见的错误。通过分析e.code属性在不同事件类型中的适用性,我们将深入探讨touchstart事件的正确处理方式,并提供示例代码和最佳实践,确保您的游戏在移动设备上提供无缝的交互体验。
理解事件类型与事件对象属性
在开发javascript游戏时,将原本基于键盘输入的交互逻辑适配到触控设备是一个常见需求。然而,许多开发者在尝试将keypress或keydown事件直接替换为touchstart时,会遇到意料之外的问题。这通常源于对不同事件类型所附带的事件对象(Event Object)属性的误解。
以Flappy Bird为例,当我们需要将小鸟的跳跃动作从按下空格键(Space)改为触摸屏幕时,直观地将document.addEventListener(“keypress”, handleJump)改为document.addEventListener(“touchstart”, handleJump),并将handleJump函数中的条件判断从e.code !== “Space”改为e.code !== “touchstart”,看似合理,实则不然。
问题的核心在于e.code属性。e.code是KeyboardEvent(键盘事件)对象特有的属性,用于表示触发事件的物理按键代码,例如”Space”、”KeyA”等。而touchstart事件属于TouchEvent(触控事件)类型,其事件对象不包含code属性。因此,当touchstart事件触发时,e.code将是undefined。将undefined !== “touchstart”作为条件判断,实际上是试图比较一个不存在的属性与一个字符串,这会导致条件始终为真(或在严格模式下抛出错误),从而阻止了后续逻辑的执行。
正确处理触控事件
对于touchstart这类触控事件,我们通常不需要检查特定的“代码”来判断事件的来源,因为事件本身就代表了用户在屏幕上的触摸行为。如果事件监听器已经明确绑定到touchstart事件,那么当handleJump函数被调用时,我们就可以确信这是一个触摸事件。
因此,解决问题的关键在于移除或修改不适用于触控事件的条件判断。
立即学习“Java免费学习笔记(深入)”;
原始(错误)的 handleJump 函数:
function handleJump(e) { if (e.code !== "touchstart") return; // 错误:e.code 不适用于 touchstart 事件 timeSinceLastJump = 0; }
修正后的 handleJump 函数:
function handleJump(e) { // 对于 touchstart 事件,无需检查 e.code // 如果需要防止默认的滚动/缩放行为,可以添加 e.preventDefault(); // e.preventDefault(); timeSinceLastJump = 0; }
通过移除if (e.code !== “touchstart”) return;这一行,handleJump函数在touchstart事件触发时将直接执行timeSinceLastJump = 0;,从而正确地触发小鸟的跳跃动作。
示例代码:Flappy Bird 触控适配
以下是基于原始代码进行修改的示例,展示了如何正确地将Flappy Bird游戏适配为触控友好型。
f.js (主页面逻辑):
import { updateBird, setupBird, getBirdRect } from "./Bird.JS"; import { updatePipes, setupPipes, getPassedPipesCount, getPipesRects, } from "./Pipe.js"; // 监听 touchstart 事件来启动游戏 document.addEventListener("touchstart", handleStart, { once: true }); const title = document.querySelector("[data-title]"); const subtitle = document.querySelector("[data-subtitle]"); let lastTime; function updateLoop(time) { if (lastTime == null) { lastTime = time; window.requestAnimationFrame(updateLoop); return; } const delta = time - lastTime; updateBird(delta); updatePipes(delta); if (checkLose()) return handleLose(); lastTime = time; window.requestAnimationFrame(updateLoop); } function checkLose() { const birdRect = getBirdRect(); const insidePipe = getPipesRects().some((rect) => isCollision(birdRect, rect) ); const outsideWorld = birdRect.top < 0 || birdRect.bottom > window.innerHeight; return outsideWorld || insidePipe; } function isCollision(rect1, rect2) { return ( rect1.left < rect2.right && rect1.top < rect2.bottom && rect1.right > rect2.left && rect1.bottom > rect2.top ); } function handleStart() { title.classlist.add("hide"); setupBird(); setupPipes(); lastTime = null; window.requestAnimationFrame(updateLoop); } function handleLose() { setTimeout(() => { title.classList.remove("hide"); subtitle.classList.remove("hide"); subtitle.textContent = `${getPassedPipesCount()} Pipes`; // 游戏结束后,再次监听 touchstart 来重新开始游戏 document.addEventListener("touchstart", handleStart, { once: true }); }, 100); }
Bird.js (小鸟逻辑):
const birdElem = document.querySelector("[data-bird"); const BIRD_SPEED = 0.5; const JUMP_DURATION = 120; let timeSinceLastJump = Number.POSITIVE_INFINITY; export function setupBird() { setTop(window.innerHeight / 2); // 移除旧的事件监听器,确保只添加一次 document.removeEventListener("touchstart", handleJump); // 监听 touchstart 事件来触发跳跃 document.addEventListener("touchstart", handleJump); } export function updateBird(delta) { if (timeSinceLastJump < JUMP_DURATION) { setTop(getTop() - BIRD_SPEED * delta); } else { setTop(getTop() + BIRD_SPEED * delta); } timeSinceLastJump += delta; } export function getBirdRect() { return birdElem.getBoundingClientRect(); } function setTop(top) { birdElem.style.setProperty("--bird-top", top); } function getTop() { return parseFloat(getComputedStyle(birdElem).getPropertyValue("--bird-top")); } function handleJump(e) { // 修正:移除对 e.code 的错误判断 // 建议:对于游戏中的触控事件,通常需要阻止默认行为以防止页面滚动或缩放 e.preventDefault(); timeSinceLastJump = 0; }
注意事项与最佳实践
- e.preventDefault(): 在处理触控事件时,尤其是在游戏中,强烈建议调用e.preventDefault()。这可以阻止浏览器执行与触摸相关的默认行为,例如页面滚动、缩放或触发上下文菜单。如果您的游戏不阻止这些默认行为,用户可能会在尝试玩游戏时意外地滚动页面。
- 事件监听器的管理: 在setupBird函数中,先removeEventListener再addEventListener是一个好习惯,可以防止重复添加相同的事件监听器,尤其是在游戏重置或重新开始时。
- click事件与touchstart/touchend:
- 多点触控: 如果您的游戏需要支持多点触控(例如,同时控制多个角色或执行组合操作),您需要利用TouchEvent对象的e.touches、e.targetTouches和e.changedTouches属性来获取当前所有触摸点的信息。
- 性能优化: 频繁触发的触控事件(如touchmove)可能会影响性能。如果不需要实时追踪触摸点的移动,尽量避免在这些事件上执行复杂的计算。
总结
将JavaScript游戏从键盘控制转换为触控控制时,理解不同事件类型(如KeyboardEvent和TouchEvent)的事件对象属性至关重要。e.code是键盘事件特有的属性,不适用于触控事件。对于touchstart事件,我们只需监听事件并执行相应的逻辑,同时考虑使用e.preventDefault()来优化用户体验。通过遵循这些原则和最佳实践,您可以确保您的JavaScript游戏在移动设备上提供流畅、响应迅速的触控交互。
评论(已关闭)
评论已关闭