作为一名网页开发者,我密切关注着电子游戏的界面设计。从守望先锋的 HUD 到口袋妖怪GO 的捕捉画面,再到拓荒者游戏的狩猎场景,游戏通常会包含有趣的机制和 令人满意的互动体验,其中许多启发了我自己在 Codepip 上编写的代码游戏。
除此之外,在网页栈上实现这些游戏设计的小片段是一种有趣且有效的方式,可以拓宽你的技能。通过专注于特定元素,你的时间将花在处理一个有趣的部分上,而无需构建包含所有内容的完整游戏。即使在这个有限的范围内,你通常也会接触到新的技术和技巧,这些技术和技巧突破了你开发知识的界限。
作为这个想法的案例研究,我将带你了解我重制 Among Us 中卡片滑动的过程。对于那些不了解的人来说,Among Us 是一款流行的多人游戏。在宇宙飞船上,船员必须推断出他们中谁是冒名顶替者。同时,他们要完成例行维护任务,并避免被冒名顶替者杀害。
卡片滑动是最臭名昭著的维护任务之一。尽管它很简单,但许多玩家都在努力完成它,以至于它成为了直播和模因的素材。
这是我的演示
这是我重制卡片滑动任务的版本
接下来,我将带你了解一些我用来创建这个演示的技术。
使用鼠标和触摸事件滑动
在快速用代码构建主要组件后,我必须让卡片可拖动。在游戏中,当你开始拖动卡片时,它会水平地跟随你的指针位置,但垂直地保持与读卡器对齐。卡片在向左或向右拖动时,超出读卡器范围的距离有限。最后,当你松开鼠标或手指时,卡片会返回到其原始位置。
所有这些都是通过将函数分配给鼠标和触摸事件来实现的。只需要三个函数来处理鼠标按下、鼠标移动和鼠标抬起(或触摸开始、触摸移动和触摸结束,如果你使用的是触摸屏设备)。以下是该 JavaScript 代码的框架
const card = document.getElementById('card');
const reader = document.getElementById('reader');
let active = false;
let initialX;
// set event handlers
document.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
document.addEventListener('touchstart', dragStart);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', dragEnd);
function dragStart(e) {
// continue only if drag started on card
if (e.target !== card) return;
// get initial pointer position
if (e.type === 'touchstart') {
initialX = e.touches[0].clientX;
} else {
initialX = e.clientX;
}
active = true;
}
function drag(e) {
// continue only if drag started on card
if (!active) return;
e.preventDefault();
let x;
// get current pointer position
if (e.type === 'touchmove') {
x = e.touches[0].clientX - initialX;
} else {
x = e.clientX - initialX;
}
// update card position
setTranslate(x);
}
function dragEnd(e) {
// continue only if drag started on card
if (!active) return;
e.preventDefault();
let x;
// get final pointer position
if (e.type === 'touchend') {
x = e.touches[0].clientX - initialX;
} else {
x = e.clientX - initialX;
}
active = false;
// reset card position
setTranslate(0);
}
function setTranslate(x) {
// don't let card move too far left or right
if (x < 0) {
x = 0;
} else if (x > reader.offsetWidth) {
x = reader.offsetWidth;
}
// set card position on center instead of left edge
x -= (card.offsetWidth / 2);
card.style.transform = 'translateX(' + x + 'px)';
}
performance.now()
设置状态
使用 接下来,我必须确定卡片滑动是有效还是无效。要使它有效,你必须以恰当的速度将卡片拖过读卡器。拖动距离不够远?无效。速度太快?无效。速度太慢?无效。
为了找出卡片是否已滑动足够远,我在 dragEnd
函数中检查了卡片相对于读卡器右边缘的位置。
let status;
// check if card wasn't swiped all the way
if (x < reader.offsetWidth) {
status = 'invalid';
}
setStatus(status);
为了测量卡片滑动的持续时间,我分别在 dragStart
和 dragEnd
中使用 performance.now()
设置了开始和结束时间戳。
function setStatus(status) {
// status is only set for incomplete swipes so far
if (typeof status === 'undefined') {
// timestamps taken at drag start and end using performance.now()
let duration = timeEnd - timeStart;
if (duration > 700) {
status = 'slow';
} else if (duration < 400) {
status = 'fast';
} else {
status = 'valid';
}
}
// set [data-status] attribute on reader
reader.dataset.status = status;
}
根据每个条件,将在读卡器的 data-status
属性上设置不同的值。CSS 用于显示相关消息并点亮红色或绿色指示灯。
#message:after {
content: "Please swipe card";
}
[data-status="invalid"] #message:after {
content: "Bad read. Try again.";
}
[data-status="slow"] #message:after {
content: "Too slow. Try again.";
}
[data-status="fast"] #message:after {
content: "Too fast. Try again.";
}
[data-status="valid"] #message:after {
content: "Accepted. Thank you.";
}
.red {
background-color: #f52818;
filter: saturate(0.6) brightness(0.7);
}
.green {
background-color: #3dd022;
filter: saturate(0.6) brightness(0.7);
}
[data-status="invalid"] .red,
[data-status="slow"] .red,
[data-status="fast"] .red,
[data-status="valid"] .green {
filter: none;
}
使用字体、动画和音频进行最终润色
核心功能完成后,我添加了一些额外的细节,使项目看起来更像 Among Us。
首先,我使用了一种名为 DSEG 的免费自定义字体来模仿旧式 LCD 的分段式字体。只需要托管文件并在 CSS 中 声明字体样式 即可。
@font-face {
font-family: 'DSEG14Classic';
src: url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.woff2') format('woff2'),
url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.woff') format('woff'),
url('../fonts/DSEG14-Classic/DSEG14Classic-Regular.ttf') format('truetype');
}
接下来,我复制了原始文本中的抖动动画。游戏开发者通常会添加细微的动画来为元素注入活力,例如让背景漂移或让角色呼吸。为了实现抖动效果,我定义了一个 CSS 动画
@keyframes jitter {
from {
transform: translateX(0);
}
to {
transform: translateX(5px);
}
}
此时,文本会平滑地来回滑动。但是,我想要的是它每次来回跳动 5 像素。这里可以使用 steps()
函数
#message {
animation: jitter 3s infinite steps(2);
}
最后,我添加了与 Among Us 中相同的音频反馈。
let soundAccepted = new Audio('./audio/CardAccepted.mp3');
let soundDenied = new Audio('./audio/CardDenied.mp3');
if (status === 'valid') {
soundAccepted.play();
} else {
soundDenied.play();
}
在网页开发领域,音效通常不受欢迎。像这样的项目提供了充分利用音频的机会。
就这样,我们完成了!再次展示演示
尝试你自己的
鉴于网络在外观和感觉方面的标准化程度,这种从游戏中提取元素并将其在网络中实现的方法,是一种打破舒适区并尝试新事物的不错方式。
以 Among Us 的卡片滑动为例。在一个小型且简单的演示中,我使用 CSS 对网页字体和动画进行了调整。我使用 JavaScript 对输入事件和音频进行了修改。我尝试了非传统的视觉风格。
现在,是时候从你最喜欢的游戏中调查有趣的机制,并尝试复制它们了。你可能会惊讶于自己的学习成果。