Към съдържанието
PN Scripts

Игри

Клавиатура, геймпад и пренастройваемо управление в браузърна игра: физически клавиши, мъртви зони и честни подсказки

  • PN Scripts
  • 7 мин четене

Управлението е първото, което играчът преценява, и последното, което много браузърни игри правят добре. Игра, която ползва WASD по буква, не може да се играе на френска клавиатура, игра, която иска Escape за пауза, се бие с излизането от цял екран в браузъра, а игра, която показва подсказки за клавиатура, докато държите геймпад, изглежда недовършена. Това ръководство минава през слоя за управление на нашите 3D браузърни игри: как се четат клавишите, така че всяка подредба да работи, как се чете Gamepad API, как се пази пренастройката и кои настройки за достъпност стоят до нея. Кодът е съкратен от собствения модул за управление на игрите.

#Четете физическите клавиши, показвайте местните надписи

KeyboardEvent носи две различни неща. key е знакът, който клавишът дава в текущата подредба, а code е физическото място на клавиша, наречено по американската подредба. За движение и действия ви трябва мястото: клавишът под 2 и до Tab е KeyW, независимо дали подредбата го нарича W, Z при AZERTY или кирилска буква при българска подредба. Свързвайте действията с кодове:

export function defaultBindings() {
  return {
    keys: {
      up: ['KeyW', 'ArrowUp'], down: ['KeyS', 'ArrowDown'],
      left: ['KeyA', 'ArrowLeft'], right: ['KeyD', 'ArrowRight'],
      strike: ['Space', 'KeyJ'], raise: ['KeyK'], leap: ['ShiftLeft', 'ShiftRight', 'KeyL'],
      pause: ['KeyP', 'Escape'], mute: ['KeyM'],
    },
    pad: { up: [12], down: [13], strike: [0, 7], raise: [2, 6], leap: [1, 5], pause: [9] },
  };
}

Обратният проблем се появява в подсказките. Ако играта каже „натиснете W“ на играч, чийто клавиш е надписан Z, той търси грешния клавиш. Браузърите на Chromium предлагат navigator.keyboard.getLayoutMap(), което превръща код в надписа от подредбата на играча. Функцията е отбелязана като експериментална и я няма във Firefox и Safari, затова я ползвайте, когато я има, а иначе показвайте името на кода:

let layoutMap = null;
navigator.keyboard?.getLayoutMap?.().then((m) => (layoutMap = m)).catch(() => {});

export function keyLabel(code) {
  const named = { Space: 'Space', ShiftLeft: 'Shift', ArrowUp: '↑', Escape: 'Esc' };
  if (named[code]) return named[code];
  const local = layoutMap?.get(code);
  if (local) return local.toUpperCase();
  if (code.startsWith('Key')) return code.slice(3);
  if (code.startsWith('Digit')) return code.slice(5);
  return code;
}

#Не се бийте с браузъра

Игра в уеб страница дели клавиатурата с браузъра и няколко правила предотвратяват най-честите оплаквания:

  • Никога не изисквайте Escape. Браузърите го ползват за изход от цял екран и за освобождаване на заключения показалец, а много сайтове за игри го пазят за себе си. Нашият клавиш за пауза е P; Escape работи само като допълнителен псевдоним и играта се играе напълно без него.
  • Оставете клавишните комбинации на мира. Ако е задържан Ctrl, Alt или Command, обработката спира веднага, така че Ctrl+W затваря раздела, както очаква играчът, а Ctrl+R презарежда.
  • Спрете превъртането, не писането. Интервалът и стрелките превъртат страницата, затова играта извиква preventDefault() за тях, но само когато събитието не е насочено към текстово поле в менюто.
  • Пускайте клавишите при загуба на фокус. Ако играчът смени прозореца, докато държи D, браузърът никога не изпраща пускането на клавиша. Изчистването на всички задържани клавиши при blur спира героя да тръгне сам нанякъде.
  • Броете натискането веднъж. Пренебрегвайте event.repeat за действията, за да не изстрелва задържаният интервал по трийсет удара в секунда.

#Четете геймпада при всеки кадър

Gamepad API има събития за свързване и откачане на контролер, но не и за бутоните. Текущото състояние се чете ръчно, веднъж на кадър, и се сравнява с предишния кадър, за да се открият нови натискания. Контролерите, които браузърът разпознава, ползват стандартната подредба: бутон 0 е долният бутон отпред (A на контролер за Xbox), 1 е B, 9 е Menu, от 12 до 15 са кръстачката, а осите от 0 до 3 са двата лоста.

pollPad() {
  const pad = [...(navigator.getGamepads?.() ?? [])].find((p) => p && p.connected);
  if (!pad) return;
  // мъртва зона, преизчислена така, че движението да тръгва от нула и да стига едно
  const dz = (v) => (Math.abs(v) < 0.18 ? 0 : (v - Math.sign(v) * 0.18) / 0.82);
  this.stickL = [dz(pad.axes[0] ?? 0), -dz(pad.axes[1] ?? 0)];
  this.stickR = [dz(pad.axes[2] ?? 0), -dz(pad.axes[3] ?? 0)];
  const now = pad.buttons.map((b) => b.pressed || b.value > 0.5);
  for (const [action, buttons] of Object.entries(this.bind.pad)) {
    for (const i of buttons) if (now[i] && !this.prev[i]) this.pressed.add(action);
  }
  this.prev = now;
}

Два детайла лесно се изпускат. Лостовете никога не застават точно в нула, така че без мъртва зона героят се плъзга; а обикновеното отрязване кара първите няколко градуса наклон да скочат направо до 18 процента скорост, затова стойността се преизчислява. Спусъците са аналогови бутони и някои контролери отчитат pressed много рано в хода им, затова приемаме и стойност над половината.

#Показвайте подсказките за устройството в ръката

Играта пази една променлива за вида управление, което играчът ползва: клавиатура и мишка, докосване или геймпад. Тя се сменя при всяко истинско събитие: натиснат клавиш или движение на мишката превключва на клавиатура, докосване на сензорно управление, наклон на лост или бутон на геймпад. Всяка подсказка на екрана чете тази променлива, така че обучението казва „Space“ преди миг и „A“, щом вземете контролера. Телефонът започва в режим на докосване, а лаптоп със сензорен екран остава на мишка, докато ползвате мишката. Звучи като козметика, а е един от най-ясните признаци за завършена игра.

#Оставете играчите да пренастроят всичко

Пренастройката не е лукс. Трябва на играчите левичари, на хората с ограничена подвижност, на играчите с необичайни клавиатури и на всеки, който не е съгласен с подразбиращите се клавиши. В нашите настройки всяко действие показва текущия си клавиш и бутон; когато го изберете, се появява „Натиснете клавиш или бутон…“ и следващият клавиш или бутон става новото свързване:

  • Escape отменя, вместо да бъде свързан.
  • Бутон на геймпад се брои едва след 250 милисекунди, за да не се хване натискането, с което е отворена подканата.
  • Клавиш, който вече принадлежи на друго действие, се взима от него, така че две действия никога не делят клавиш по случайност.
  • Един бутон връща подразбиращите се стойности.

Свързванията се пазят заедно с останалите настройки. При зареждане запазените свързвания се наслагват върху подразбиращите се, така че действие, добавено в по-късна версия, пак получава своя клавиш, вместо да остане без такъв. Статията ни за слотове за запис в браузърна игра с localStorage показва това наслагване подробно.

#Настройки за достъпност, които стоят до управлението

По Game Accessibility Guidelines екранът за управление е само част от картината. Billowpost, нашата игра с балон, предлага обърната вертикална камера, намалено движение (без тресене на камерата и без проблясвания), висок контраст за интерфейса, режим за хора с цветна слепота, който добавя преброими чертички към всяка лента на вятъра на скалата за височина, така че лентите се различават и без цвят, три размера на текста, помощник, който задържа височината, когато пуснете горелката, и скорост на играта 100, 85 или 70 процента. Няколко от тях са евтини само защото правилата вървят на фиксирана стъпка; статията ни за цикъла с фиксирана стъпка обяснява защо.

Докосването е отделна тема. Ръководството ни за управление с докосване, което работи и с клавиатура и геймпад е писано за Godot 4, но идеите важат и тук, а добавките за Godot Pipalo и Podkanka ги събират за този двигател. Браузърните ни игри са в каталога с игри на PN Scripts.

#Често задавани въпроси

event.key или event.code да ползвам за управлението на играта?

Ползвайте event.code за действията, защото назовава физическия клавиш и работи на всяка подредба. Ползвайте event.key само за въвеждане на текст и показвайте надписите от подредбата на играча, когато браузърът ги дава.

Защо бутоните на геймпада не изпращат събития?

Gamepad API изпраща събития само при свързване или откачане на контролер. Четете navigator.getGamepads() веднъж на кадър и сравнявайте всеки бутон с предишния кадър, за да откриете натискания.

Каква мъртва зона да ползва браузърната игра?

Около 0,15 до 0,2 от хода на лоста върши работа за повечето контролери. Преизчислете стойността след мъртвата зона, така че движението да тръгва от нула и пак да стига пълна скорост на ръба.

Безопасно ли е Escape да е клавишът за пауза?

Не и като единствен. Браузърите ползват Escape за изход от цял екран и за освобождаване на показалеца, а някои сайтове за игри го пазят за себе си. Дайте друг клавиш за пауза и оставете Escape като псевдоним.

#Източници

Коментари

Коментари

Напишете първия коментар.

Оставете коментар

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения и API по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук