Använda HTML5 Gamepad API för att lägga till Controller Support till Browserspel

Eftersom webbaserat spel blir mer populärt är en av de största klibbiga punkterna för spelare inmatningskontroll. Medan mina första FPS-spel var rent mus- och tangentbordsbaserade har jag nu blivit mycket mer van vid en konsolkontrollant som jag hellre skulle använda den till allt, inklusive webbaserade spel. 

Lyckligtvis finns HTML5 Gamepad API för att tillåta webbutvecklare programmatisk åtkomst till spelkontrollers. Olyckligtvis, även om det här API: n har funnits länge, är det bara nu långsamt som går in i de senaste versionerna av skrivbordsbläsare. Det languished länge i en byggnad av Firefox (inte en bygga högre, ingen nattlig bygga) och var problematisk i Chrome. Nu är det bra, inte perfekt, men lite mindre problematisk och faktiskt ganska lätt att använda. 

I den här artikeln ska jag diskutera de olika funktionerna i API: n, hur du får det att fungera i både Firefox och Chrome, och visa ett verkligt (om enkelt) spel och hur enkelt det är att lägga till gamepad-stöd för det.

Det grundläggande

Gamepad API omfattar följande funktioner:

  • Möjligheten att lyssna på ansluta och koppla från evenemang.
  • Möjligheten att känna igen flera gamepads. (I teorin kan du ansluta så många spelportar som du har USB-portar.)
  • Möjligheten att inspektera dessa gamepads och känna igen hur många axlar de har (joysticks), hur många knappar de har (har du spelat en modern spelkonsol på sistone?), Och vilken stat varje av dessa enskilda objekt är i.

Låt oss börja med att diskutera hur du kan upptäcka stöd för en gamepad på hög nivå. 

Både Firefox och Chrome stöder en metod på navigatör, getGamepads (), som returnerar en grupp av alla anslutna gamepad-enheter. Vi kan använda detta som en enkel metod för att upptäcka om Gamepad API är närvarande. Här är en enkel funktion för den kontrollen:

funktion canGame () returnera "getGamepads" i navigatorn; 

Än så länge är allt bra. Nu för den funky delen. Gamepad API har stöd för händelser som upptäcker när en gamepad är ansluten och frånkopplad. Men vad händer om användaren redan har en gamepad ansluten till sin bärbara dator när de träffar din sida? Normalt väntar webbsidan på att användaren gör något, någonting riktigt, med den faktiska spelporten. Det innebär att vi måste ge någon typ av meddelande till användaren som låter dem veta att de behöver "väcka" stöd för gamepad om den är ansluten. Du kan berätta för dem att slå någon knapp eller flytta en pinne. 

För att göra sakerna ännu mer intressanta verkar den här kontrollen inte vara nödvändig när du laddar om sidan. Du kommer att märka att när du har använt Gamepad API på en sida och sedan laddat den igen, känner sidan igen detta faktum och anser det automatiskt vara anslutet.

Men vänta-det blir bättre. Chrome stöder inte de anslutna (eller bortkopplade) händelserna just nu. Det typiska arbetet för detta (och det som visas i de goda MDN-dokumenten för API: n) är att ställa in en omröstning och se om en gamepad "visas" i listan över anslutna enheter.

Förvirrande? Låt oss börja med ett exempel som bara stöder Firefox:

           

I exemplet ovan börjar vi med att kontrollera om webbläsaren stöder Gamepad API. Om det gör det uppdaterar vi först en div med instruktioner för användaren och börjar sedan lyssna omedelbart till båda ansluta och koppla från evenemang. 

Om du kör det här med Firefox och kopplar din gamepad, måste du då också trycka på en knapp, vid vilken tidpunkt händelsen är avfyrade och du är redo att gå. 

Men i min testning, när jag laddar om sidan, förbindelse händelsen är omedelbar. Detta skapar en liten "flimmer" -effekt som kan vara oöverskridlig. Du kan faktiskt använda ett intervall för att ange riktningarna för något som 250ms efter att DOM har laddats och bara fråga om en anslutning inte inträffade under tiden. Jag bestämde mig för att hålla saker enkelt för denna handledning.

Vår kod fungerar för Firefox, men nu lägger vi till Chrome-support:

           

Koden är lite mer komplex nu, men inte hemskt så. Ladda demo i Chrome och se vad som händer.

Observera att vi har en ny global variabel, hasGP, att vi ska använda som en allmän flagg för att ha en gamepad ansluten. Som tidigare har vi två evenemangslytare, men nu har vi ett nytt intervall som ställs in för att se om en spelad sida finns. Det här är första gången du har sett getGamepads i åtgärd, och vi beskriver det lite mer i nästa avsnitt, men vet för tillfället att det bara returnerar en array och om det första objektet finns kan vi använda det som ett sätt att veta att en gamepad är ansluten. 

Vi använder jQuery för att avfyra samma händelse Firefox skulle ha fått, och sedan radera intervallet. Observera att samma intervall kommer att elda en gång i Firefox, vilket är lite slöseri, men jag trodde att det var slöseri med tid att lägga till ytterligare stöd för att snusa Chrome mot Firefox. Ett litet samtal som detta slösat i Firefox borde inte betyda alls.

Nu när vi har en ansluten gamepad, låt oss arbeta med det!

Gamepad-objektet

För att ge dig en uppfattning om hur gammal jag är - här är toppmodern joystick som jag använde för mitt första spelsystem.


Bild från Wikimedia Commons.

Trevligt - enkelt - och det skadade som helvete efter en timmes spelande. Moderna konsoler har mycket mer komplexa gamepads. Tänk på PS4-kontrollenheten:

Bild från Wikimedia Commons.

Den här styrenheten har två pinnar, en riktningsplatta, fyra huvudknappar, fyra mer på baksidan, a Dela med sig och alternativ knapp, a PS knapp, lite funky touch-kontroll, en högtalare och ett ljus. Det har också förmodligen en fluxkapacitat och en kökshandfat. 

Lyckligtvis har vi tillgång till detta djur via Gamepad-objektet. Egenskaperna inkluderar:

  • id: Detta är namnet på regulatorn. Förvänta dig inte något vänligt från detta. Min DualShock 4 rapporterades som 54c-5c4-trådlös kontroller i Firefox, medan Chrome heter samma kontroller Trådlös kontroller (STANDARD GAMEPAD-leverantör: 054c Produkt: 05c4).
  • index: Eftersom API: n Gamepad stöder flera kontroller kan du bestämma vilken numrerad kontroller den är. Det kan användas för att identifiera spelare en, två och så vidare.
  • kartläggning: Kartläggning är inte något vi kommer att täcka här, men i huvudsak är det här någonting webbläsaren kan göra för att hjälpa till att kartlägga din speciella kontroller till en "standard" -inställningar. Om du har spelat flera konsoler vet du att de har vissa likheter när det gäller kontroll, och API försöker att "mash" din controller till en standard. Du behöver inte oroa dig för det här för tillfället, men om du vill ha mer information, kolla mappningsavsnittet i API-dokumenten.
  • ansluten: En booleska som indikerar om styrenheten fortfarande är ansluten.
  • knappar: En rad knappvärden. Varje knapp är en förekomst av GamepadButton. Observera att GamepadButton objektet stöder både en enkel boolesisk egendom (nedtryckt) samt a värde egendom för analoga knappar.
  • axlar: En uppsättning värden som representerar de olika pinnarna på gamepad. Med en gamepad med tre pinnar kommer du att ha en uppsättning av sex objekt, där varje pinne representeras av två arrayvärden. Den första i paret representerar X, eller vänster / höger rörelse, medan den andra representerar Y, upp / ner-rörelse. I alla fall varierar värdet från -1 till 1: för vänster / höger värden, -1 är kvar och 1 är rätt; för upp / ner värden, -1 är upp och 1 är nere. Enligt API sorteras arrayen enligt "vikt", så i teorin kan du fokusera på axlar [0] och axlar [1] för de flesta spelbehov. För att göra sakerna mer intressanta, med hjälp av min DualShock 4 rapporterade Firefox tre axlar (vilket är meningsfullt - se bilden ovan), men Chrome rapporterade två. Det verkar som om d-pad-pinnen rapporteras i Firefox som en axel, men ingen data verkar komma ut ur den. I Chrome visade d-pennan som ytterligare knappar och lästes korrekt.
  • tidsstämpel: Slutligen är detta värde en tidsstämpel som representerar den sista gången hårdvaran kontrollerades. I teorin är det förmodligen inte något du skulle använda.

Okej, så det är mycket att smälta. I exemplet nedan har vi helt enkelt lagt till ett intervall för att få och inspektera den första spelporten och skriva ut ID och sedan knapparna och axlarna:

           

Du kan prova demoen i Chrome eller Firefox.

Jag antar att detta är allt ganska självförklarande; Den enda riktiga svåra delen var att hantera axlarna. Jag slingrar över matrisen och räknar med två gånger för att representera både vänster / höger, upp / ner värden samtidigt. Om du öppnar upp det här i Firefox och ansluter en DualShock kan du se något så här.

Som du kan se, pressades knapp 2 när jag tog min skärmdump. (Om du är nyfiken, så var det X knappen.) Notera pinnarna; min gamepad satt på min bärbara dator och de värdena var ständigt fluktuerade. Inte på ett sätt som skulle innebära att värdena var dålig, i sig - om jag plockade upp spelplattan och tryckte hela vägen i en riktning såg jag rätt värde. Men jag tror vad jag såg var hur känslig regulatorn är för miljön. Eller kanske gremlins.

Här är ett exempel på hur Chrome visar det:

Jag höll igen X knapp - men märk hur knappindexet är annorlunda här. Som du kan berätta, kommer du behöva göra lite ... massera om du vill använda detta API för ett spel. Jag skulle föreställa mig att du kunde kontrollera båda knapparna 1 och 2 för "brand" och följa upp med en hel del testning.

Få alltid att falla på plats

Så, hur är det med en riktig demo? Precis som de flesta kodare som började sitt liv spela videospel drömde jag om att vara en hotshot videospelskapare när jag växte upp. Det visar sig att matte blir riktigt hård efter kalkylen, och tydligen har dessa "web" saker en framtid, så medan den framtiden inte föll ut för mig, skulle jag fortfarande vilja föreställa mig att jag en dag kunde vända dessa webbstandarder färdigheter till ett spelbart spel. Fram till den dagen, vad jag har idag är en ganska lammad kanvasbaserad version av pong. Singelspelare pong. Som jag sa, lama.

Spelet gör enkelt en paddla och en boll och ger dig tangentbordskontroll över bollen. Varje gång du saknar bollen, går poängen upp. Vilket är meningsfullt för golf snarare än pong, antar jag, men låt oss inte oroa oss för mycket om det. Koden finns i game1.html och du kan spela demo i din webbläsare. 

Jag kommer inte att gå igenom hela koden här, men låt oss titta på några utdrag. Först här är huvudslagsfunktionen som hanterar alla animationsdetaljer:

funktionsling () draw.clear (); ball.move (); ball.draw (); paddle.draw (); paddle.move (); draw.text ("Score:" + poäng, 10, 20, 20); 

Padden drivs av tangentbordet med två enkla händelsehanterare:

$ (fönster) .keydown (funktion (e) switch (e.keyCode) case 37: input.left = true; break; case 39: input.right = true; break;); $ (fönster) .keyup (funktion (e) switch (e.keyCode) fall 37: input.left = false; break; case 39: input.right = false; break;); 

De inmatning variabeln är en global variabel som plockas upp av ett paddelobjekt flytta metod:

this.move = function () if (input.left) this.x - = this.speed; if (this.x < 0) this.x=0;  if(input.right)  this.x += this.speed; if((this.x+this.w) > canvas.width) this.x = canvas.width-this.w;  

Återigen, inget för komplicerat här. Här är en skärmdump av spelet i aktion. (Jag vet-jag borde inte sluta mitt dagjobb.)

Så, hur lägger vi till gamepad support? Lyckligtvis har vi redan gjort koden för oss. I den tidigare demo gjorde vi allt som krävdes för att kontrollera och uppdatera koden. Vi kan ta den koden och helt enkelt lägga till den i spelets befintliga kod. 

Eftersom det är (praktiskt taget) detsamma, kommer jag inte att upprepa det (även om hela listan är tillgänglig om du vill ha det), men jag delar den ändrade kodkörningen varje 100ms när en gamepad detekteras:

funktion checkGamepad () var gp = navigator.getGamepads () [0]; var axeLF = gp.axes [0]; if (axeLF < -0.5)  input.left = true; input.right = false;  else if(axeLF > 0,5) input.left = false; input.right = true;  annat input.left = false; input.right = false;  

Återigen kan du prova demoen i endera webbläsaren.

Som med föregående exempel har vi antagit att vi bara bryr oss om en gamepad. Eftersom vårt spel bara har en paddla och den bara rör sig horisontellt, kan vi fortsätta genom att bara kontrollera den allra första axeln. Kom ihåg att enligt API bör detta vara den "viktigaste" en och i min test var det den vänstra pinnen, vilket är ganska standard för spel. 

Eftersom vårt spel använder en global variabel, inmatning, att representera vänster och höger rörelse, allt jag behöver göra är att ändra det värdet baserat på axelvärdet. Nu märker jag att jag inte bara letade efter "mindre än noll" och "större än noll". Varför? Om du kommer ihåg från tidigare demo var gamepad mycket känslig och skulle ofta rapportera värden även när jag inte trodde att jag faktiskt hade flyttat pinnen. Använda ett gränsvärde på .5 ger kontrollen lite mer stabilitet. (Och självklart är det den typen av saker som du behöver tweak för att se vad "känns" rätt.) 

Sammantaget lade jag till ungefär 25 linjer kod till mitt spel för att lägga till gamepad support. Det stenar.

Spel på!

Förhoppningsvis har du sett det, medan det finns vissa idiosynkraser, har Gamepad API nu stöd i två stora webbläsare, och det är något jag tycker utvecklare borde börja börja överväga för sina spel.

Medel

Här är några extra resurser som hjälper dig att lära dig mer om Gamepad API.

  • Använda Gamepad API
  • Gamepad Specifikation
  • Gamepad.js - Ett Javascript-bibliotek för att möjliggöra användning av gamepads och joysticks i webbläsaren.
  • Gamepad kontroller för HTML5-spel
  • Wii U: s version (helt annorlunda än specifikationen - bra jobb, Nintendo!)

referenser

  • Förhandsvisa bildkredit: Video Game Controller utformad av Uriel Sosa från Noun Project