Construyendo un juego de pong usando deepsteam.io

Introducción

Este tutorial rápido y simple le explicará cómo convertir un juego de navegador existente en un juego multijugador en tiempo real usando deepstream.io y solo unas pocas líneas de código; también aprenderá a usar su dispositivo móvil como controlador.


Esta publicación es una publicación invitada del equipo del proyecto deepstream.io.

Elegir un juego

Hay una buena implementación de Pong para navegadores en Github .

Está escrito como un tutorial con 5 partes.

Sin embargo, el modo multijugador está limitado a un teclado compartido.

Mejoremos eso agregando estas características:

1: permite que los usuarios jueguen con su dispositivo móvil.

2: permitir controles (inicio y parada) desde el dispositivo.


3: permite jugar en pantallas táctiles.

Arquitectura del juego

Al mantener toda la lógica original en el navegador, puede funcionar como nuestro servidor de juegos, lo que le permite realizar un seguimiento de la puntuación y el estado del juego.

Cada jugador abre la segunda página en su móvil para controlar la paleta de pong.

Los datos de los jugadores (entrada de control y estado) se sincronizan en tiempo real entre las páginas a través de deepstream.

Generando un código QR

Los jugadores pueden unirse fácilmente al juego usando un código QR. Integraremos un código para cada jugador en la barra lateral del juego.

index.html
< Div clase = “barra lateral barra lateral derecha” >
  < h2 > Jugador 2 < / h2 >
  < un target = “_blank” href = “/controls.html#2” rel = “noreferrer noopener” >
    < div clase = “qrcode ” id = ” qrcode2 “ > < / div >
  < / a >
  < div class = ” online online-2 “> en línea < /div >
< / div >
< div clase = “barra lateral” >
  < h2 >Jugador 1< / h2 >
  < un target = “_blank” href = “/controls.html#1” rel = “noopener noreferrer” >
    < div clase = “qrcode” id = “qrcode1” > < / div >
  < / a >
  <div class =“online online-1” > online < / div >
< / div >

Para crear el código QR usaremos el módulo npm qrcodejs2 que puede generar el código en el navegador.

src / bootstrap.js
opciones const = {

  ancho
: 128 ,

  alto
: 128 ,

  colorDark
: “# 000000” ,

  colorLight
: “#ffffff” ,

  correctLevel
: QRCode. CorrectLevel . H
}
nueva QR ( documento. GetElemenById ( “qrcode1” ) , objeto . Asignar ( {

  texto
: . Ventana de ubicación . Origen + ‘/controls.html#1’
} , opciones ) )
nuevo QRCode ( document. getElementById ( “qrcode2” ) , Object . assign ( {

  texto
: ventana. ubicación . origen + ‘/controls.html#2’
} , opciones ) )

Aquí está el resultado de lo mismo.

Página del controlador

controles.html

< html >
  < head >
    < title > pong controller < / title >
    < meta http-equiv = “Content-Type” content = “text / html; charset = utf-8” / >
    < meta name = “viewport” content = “width = device-width, user-scalable = no” >
    < link href = “style / controls.css” media = “screen, print” rel = “hoja de estilo ” tipo =“text / css” / >
  < / head >
  < cuerpo >
    < div class = “gamepad-container” >
      < button class = “gamepad gamepad-down” > < / button >
      < button class = “gamepad gamepad-up” > < / button >
    < / div >
    < script src = “node_modules / deepstream.io-client-js / dist / deepstream.js ” > < /script >
    < script src = “src / controls / index.js” type = “text / javascript” > < / script >
  < / body >
< / html >

Tan pronto como se carga la página del controlador, el navegador se conecta al servidor de flujo profundo e inicializa un registro para el reproductor. Este registro contiene el nombre del jugador (para simplificar, los jugadores solo se nombran 1 y 2 ) y una propiedad indica si el jugador está presionando un botón ( ‘arriba’ o ‘abajo’ ) o si el botón no está presionado ( nulo ).

src / controller / index.js
const deepstream = ventana. deepstream
const player = ventana. ubicación . hash . substr ( 1 ) || 1

// ignore la autenticación en este tutorial
const ds = deepstream ( ‘localhost: 6020’ ) . login ( { } , function ( ) {
  if ( éxito ) {
    return new Gamepad ( )
  }
})

La clase _Gamepad_ registra detectores de eventos tanto para dispositivos táctiles como para ratones.

src / controller / index.js
class Gamepad {

  constructor
( ) { botones
    constantes = documento. querySelectorAll ( ‘.gamepad’ )
    this . initializeRecords ( ‘player /’ + player )
    // sube
    esto . addEventListener ( botones [ 0 ] , [ ‘touchstart’ , ‘mousedown’ ] , esto . onButtonPress )
    esto . addEventListener (botones [ 0 ] , [ ‘mouseup’ , ‘tocar’ ] , this . onButtonRelease )
    // abajo de
    esto . addEventListener ( botones [ 1 ] , [ ‘touchstart’ , ‘mousedown’ ] , esto . onButtonPress )
    esto . addEventListener ( botones [ 1 ] , [ ‘mouseup’ , ‘touchend’ ] , esto . onButtonRelease )
  }


  addEventListener
( elemento , tipos , controlador ) {

    elemento.
addEventListener ( type , handler. bind ( this ) )
    for ( let i = 0 ; i < types. length ; i ++ ) {

      element.
addEventListener ( tipos [ i ] , handler. bind( esto ) )
    }
  }


  initializeRecords
( playerRecordName ) {
    esto . registro = ds. registro . getRecord ( playerRecordName )
    esto . registro . set ( {

      nombre
: jugador ,

      dirección
: nulo
    } )
  }


  onButtonPress
( evento ) {

    evento.
preventDefault ( )
    constobjetivo = evento. objetivo
    constante arriba = objetivo. classList . contiene ( ‘gamepad-up’ )
    const down = target. classList . contiene ( ‘gamepad-down’ )

    let direction

    if ( up ) {

      direction
= ‘up’
    } else if ( down ) {

      direction
= ‘down’
    } else {

      direction
= null
    }
    esto . updateDirection ( dirección )
  }


  updateDirection
( dirección ) {
    this . registro . set ( ‘dirección’ , dirección )
  }


  onButtonRelease
( ) {
    this . registro . set ( ‘dirección’ , nulo )
  }
}

Aquí está el resultado de lo mismo.

Conectando nuestros gamepads al juego principal

Agreguemos un código para suscribirnos a los cambios del registro en la

página
principal con el fin de actualizar la paleta.

Primero, necesitamos conectarnos al servidor de flujo profundo como lo hicimos en

la página del controlador:

src / game.js
const deepstream = require ( ‘deepstream.io-client-js’ )
const dsClient = deepstream ( ‘localhost: 6020’ ) . iniciar sesión ( )

Agregue las suscripciones de registros a la función `Runner.addEvents`

src / game.js
addEvents : function ( ) {

  Juego.
addEvent ( documento , ‘keydown’ , this . onkeydown . bind ( this ) )

  Juego.
addEvent ( documento , ‘keyup’ ,   this . onkeyup . bind ( this ) )

  const player1 = dsClient. registro . getRecord ( ‘jugador / 1’ )
  const player2= dsClient. registro . getRecord ( ‘jugador / 2’ )

  player1.
subscribe ( data => {
    this . game . updatePlayer ( 1 , data )
  } )

  player2.
subscribe ( data => {
    this . game . updatePlayer ( 2 , data )
  } )
} ,

Luego agrega estas dos funciones al objeto `Pong`:

src / pong.js
 updatePlayer : function ( player , data ) {
    if ( player == 1 ) {
      this . updatePaddle ( this . leftPaddle , data )
    } else if ( player == 2 ) {
      this . updatePaddle ( este . rightPaddle , datos )
    }
  } ,


  updatePaddle
: function (paleta , datos ) { dirección
    constante = datos. dirección
    if ( ! paddle. auto ) {
      if ( dirección === ‘arriba’ ) {

        paddle.
moveUp ( ) ;
      } else if ( dirección === ‘abajo’ ) {

        paleta.
moveDown ( ) ;
      } más si ( dirección === nulo ) {

        paleta.

        paleta
stopMovingUp ( )
. stopMovingDown ( )
      }
    }
  } ,

¡Eso es todo! Sencillo. ¡Hemos convertido el juego en un juego multijugador en tiempo real!

Espera, ¿cómo puedo jugar?

Necesita instalar deepstream y ejecutar el servidor. Para este tutorial, no necesita ninguna configuración especial, así que simplemente inicie el servidor. Hazlo ahora.

Para agrupar JavaScript y ejecutar un servidor HTTP, puede usar el script de inicio npm:

# asegúrese de que el servidor de deepstream esté ejecutando

npm start

¿Cómo puedo unirme al juego desde otro dispositivo?

Dado que usamos localhost para conectarnos al servidor de flujo profundo, el juego solo funcionará en la misma máquina, pero puedes usar este mismo software para acceder al juego.

en otras palabras: podemos abrir la página de controles en

otro navegador pero no en el teléfono inteligente u otra computadora.

Necesitamos cambiar el host de flujo profundo en src / controls / index.js a esto:

const DEEPSTREAM_HOST = ventana. ubicación . nombre de host + ‘: 6020’
const ds = deepstream ( DEEPSTREAM_HOST ) . login ( { } , función ( éxito ) {
  // …
} )

Si desea jugar usando una red WiFi, debe averiguar su dirección IP de WiFi y reemplazar localhost con su IP en el navegador de la página principal por algo como esto:

http://192.168.1.10:9966

Iniciar y detener el juego

Para mejorar la UX, agregaremos otro registro que contiene el estado actual

del juego, por ejemplo, si hay un ganador y qué jugadores están actualmente en línea.

Con esa información, los jugadores pueden iniciar y detener el juego.

Agreguemos un botón para unirse / salir a la página del controlador:

controles.html
< button class = “unirse-dejar” > unirse < / button >

Y agregue controladores a la clase Gamepad para alternar el estado entre en línea y fuera de línea.

Usamos otro registro (estado) con una propiedad para cada jugador:
player1-online y player2-online .

src / controles / index.js
class Gamepad {

  constructor
( ) {
    // …
    esto . joinButton = documento. querySelector ( ‘.join-leave’ )
    this . addEventListener ( this . joinButton , [ ‘click’ ] , this . startStopGameHandler )
  }


  initializeRecords
( playerRecordName ) {
    // …
    const statusRecord = ds. registro .getRecord ( ‘estado’ )

    statusRecord.
subscribe ( `player $ { player } online` , online` , online => {
      if ( online === true ) {

        document.
body . style . background = ‘#ccc’
        this . joinButton . textContent = ‘leave’
      } else {

        document .
cuerpo . estilo . fondo = ‘blanco’
        esto . joinButton . textContent = ‘unirse’
      }
    } , verdadero )
  }


  startStopGameHandler
( e ) {

    ds.
registro . getRecord ( ‘estado’ ) . whenReady ( statusRecord => {
      const oldValue = statusRecord. get ( `player $ { player } online` )

      statusRecord.
set (`reproductor $ { player } online` , ! oldValue )
    } )
  }
}

En la función Runner.addEvents ahora podemos escuchar el registro de estado y alternar el indicador en línea. Para comenzar el juego, ambos jugadores deben unirse al juego.

src / game.js
estado constante = dsClient. registro . estado de getRecord ( ‘estado’ )

.
subscribe ( ‘player1-online’ , online => {
  this . toggleChecked ( ‘.online-1’ , online )
  this . updateGameStatus ( online , status. get ( ‘player2-online’ ) )
} )

status.
subscribe ( ‘player2-online’ , online => {
  esto . toggleChecked ( ‘.online-2’ , en línea )
  this . updateGameStatus ( status. get ( ‘player1-online’ ) , online )
} )

y agregue una nueva función al objeto Runner :

src / game.js
updateGameStatus : function ( player1 , player2 ) {
  if ( player1 && player2 ) {
    this . juego . detener ( )
    esto . juego . startDoublePlayer ( )
  } else {
    this . juego . detener ( )
  }
}

Enviar comentarios al jugador

Démosle al usuario algunos comentarios si hiciste un gol y si él gana un partido.

El registro de estado se puede reutilizar con otra propiedad: `jugador1-goles` y` jugador2-goles`.


Podemos activar una función desde la función `Pong.goal`:

src / pong.js
esto . corredor . notificarGoal ( playerNo , esto . anota [ playerNo ] , lastGoal ) ;

y agregue la nueva función al objeto `Runner`. También podemos restablecer el

estado en línea de los jugadores si el juego ha terminado:

src / game.js
notificarObjetivo : función ( jugadorNo , goles , últimoObjetivo ) {
  const statusRecord = dsClient. registro . getRecord ( ‘status’ )
  if ( lastGoal ) {

    statusRecord.
set ( ‘player1-online’ , false )

    statusRecord.
set ( ‘player2-online’ , false )
  }

  statusRecord.
set ( `player $ { playerNo+ 1 } goals` , { cantidad : objetivos , lastGoal : lastGoal } )
} ,

Ahora necesitamos escuchar la propiedad Goals en la clase Gamepad dentro de la función initializeRecords :

src / controles / index.js
statusRecord. suscribirse ( `reproductor $ { player } goals` , datos => {
  si ( ‘vibración’ en el navegador ) {
    si ( los datos. lastGoal ) {

      navegador.
vibrar ( [ 100 , 300 , 100 , 300 , 100 ] )
    } else {

      navegador.
vibrar ( 100 )
    }
  }
} )

Usa acelerómetro para controlar la paleta

Usar los botones en un dispositivo táctil se siente un poco lento, ¿verdad ?. Así que mejorémoslo usando el

acelerómetro en su lugar.
Para simplificar el código, reemplazamos todo el código de los botones del controlador.

Reemplazamos los botones con un indicador de acelerómetro:

controles.html
< div class = “indicador-acelerómetro” > < / div >

En la clase Gamepad , adjuntamos un controlador para el evento DeviceMotionEvent . Este evento proporciona varias propiedades, pero solo necesitamos el valor
accelerationIncluyendoGravity.y . El rango de valores es de -10+10 .

Para obtener un porcentaje podemos usar esta fórmula:

vPercent = 1 – (vAbs / 20) – (1/2)
src / controles / index.js
  constructor ( ) {
    esto . indicador = documento. querySelector ( ‘. indicador-acelerómetro’ )
    if ( window. DeviceMotionEvent ! = null ) {

      window.
addEventListener ( ‘devicemotion’ , this . listenOnMotion . bind ( this ) )
    }
  }


  listenOnMotion
( e ) { valor
    constante = e.Aceleración Incluyendo Gravedad . y porcentaje
    constante = 1 ( ( valor / 10 ) 1 ) / 2 ; margen
    constante = Matemáticas . round ( porcentaje * ventana. innerHeight este . indicador . estilo . altura )
    this . indicador . estilo [ ‘margin-top’ ] = margin + ‘px’
    esto . registro . set ( ‘posición’ , porcentaje )
  }

Para la página principal, necesitamos ajustar la condición dentro de la función Pong.updatePaddle :

src / pong.js
if ( data. position ! = null ) {
  this . setPaddlePosition ( paleta , posición de datos )
}

y agregue setPaddlePosition al objeto Pong:

src / pong.js
setPaddlePosition : función ( paleta , porcentaje ) { altura
  constante = valores predeterminados. altura valores predeterminados. paddleHeight : valores predeterminados. wallWidth
  const absoluta = Matemáticas .
  paleta
redonda ( porcentaje * altura )
. setpos ( paleta. x , absoluto )
} ,

¡Finalizado!

Ahora implementamos las tres características que se mencionan al principio de este tutorial.
Auge.

Conclusión

Aprendimos cómo podemos modificar el código existente para aprovechar una nueva función usando Deepstream.io. Puede descargar el código desde aquí y ejecutarlo en su sistema.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *