Share via


Crear un juego JavaScript básico con nuestra muestra de juego

La muestra de juego táctil de JavaScript y HTML5 proporciona código útil que puedes usar como plantilla para crear una aplicación de juego de la Tienda Windows. En este tema, explicamos la manera en que trabaja la muestra y en que se puede aprovechar su código como base.

Después de leer este tema, conocerás las estrategias disponibles para un desarrollador de juegos interesado en usar HTML y JavaScrip para crear aplicaciones de la Tienda Windows.

Requisitos previos

Deberías hacer las siguientes tareas antes de leer este tema.

  • Deberías descargar la muestra de juego táctil de JavaScript y HTML5 y asegurarte de que se ejecute en tu entorno de desarrollo antes de continuar.
  • Es necesario que comprendas la programación orientada a objetos. Por ejemplo, deberías comprender conceptos; entre ellos, espacios de nombres, miembros, clases y herencias de una clase. No te preocupes si no sabes aplicar estos conceptos en JavaScript. La forma en que trabajaremos con espacios de nombres y clases en JavaScript es nueva en la Biblioteca de Windows para JavaScript.
  • También deberías comprender conceptos básicos del desarrollo de juegos, como los bucles de actualización y representación que controlan el estado del juego y la salida visual y de audio.
  • Por último, deberías echar un vistazo a los espacios de nombres WinJS.Class y WinJS.Namespace en la Biblioteca de Windows para JavaScript, que permiten la creación de clases y espacios de nombres en una aplicación JavaScript. La muestra usa el desarrollo orientado a objetos como habilita la Biblioteca de Windows para JavaScript.

Un paseo por la muestra

Los archivos HTML y CSS de la muestra proporcionan una apariencia y diseño básicos que fácilmente puedes reemplazar o personalizar. El archivo default.html importa los archivos JavaScript necesarios y define un shell que hospeda las otras páginas HTML, cada una con su propio archivo .css y .js.

Los archivos JavaScript definen la infraestructura de la aplicación y las funciones del juego. A continuación puedes ver un resumen de los archivos JavaScript de la muestra.

  • default.js: controla la navegación entre diferentes archivos HTML, como la página que controla las reglas, etc.; la integración con características de interfaz de usuario, como la barra de la aplicación; y cableado de eventos que surgen por cosas como la activación y la suspensión de la aplicación o funciones que se escribieron para controlarlos.
  • navigator.js: proporciona funciones comunes de navegación, incluida la compatibilidad con la navegación mediante teclado y el mouse.
  • homePage.js, rulesPage.js, scoresPage.js y creditsPage.js: garantiza que los archivos HTML asociados con las acciones de navegación estén conectados a los eventos apropiados. GamePage.js también tiene esta funcionalidad, pero también tiene otras funciones de las que hablaremos en su propia entrada más adelante.
  • gameState.js: realiza un seguimiento de las variables que definen el estado del juego.
  • gamePage.js: define el espacio de nombres GamePage, que administra los bucles de juego y representación, así como la posición del juego cuando se acopla o cuando cambia la orientación del dispositivo que ejecuta el juego.
  • assetManager.js: controla la carga dinámica de activos. Pasa una lista de los activos que es necesario cargar, señalando su tipo de archivo, y las funciones incluidas aquí harán el resto.
  • frameTimer.js y movingAverage.js: mide el tiempo necesario para realizar una iteración del bucle de actualización del juego basándose en el valor de fotogramas por segundo que hayas establecido, y luego programa llamadas a la función de actualización en función de esas medidas.
  • scores.js: almacena puntuaciones históricas del juego.
  • touchPanel.js: conecta eventos de toque del puntero que tienen lugar en el Canvas del juego con controladores que te ayudan a detectar el inicio y el final de una interacción en la superficie del Canvas.
  • game.js: agrupa todo lo anterior en un juego funcional: carga activos, obtiene y establece valores de configuración, actualiza el estado del juego, representa la pantalla del juego, suspende y activa el juego y delega la ejecución en cada caso a la clase adecuada definida en los archivos .js anteriores.

Jerarquía de objetos de GameManager y seguimiento del estado del juego

GameManager es el espacio de nombres principal en la muestra del juego. Contiene todas las clases y datos para el juego excepto los relacionados con los bucles de juego y de representación. La función define se usa para crear una clase, y es una buena idea ver cómo funciona a la hora de crear GameManager antes de continuar.

Piensa en este ejemplo de gameState.js, en el que se ha definido una clase llamada GameState. GameState incluye más de lo que se muestra aquí, pero vamos a destacar una colección de propiedades, llamada config. Realicemos un seguimiento de cómo una de esas propiedades, llamada frameRate, se posiciona en la gran jerarquía definida por GameManager. Éste es el momento en que frameRate se define.

var GameState = WinJS.Class.define(
    null,
{
    config: {
        // Adjust these values to configure the template itself.
        frameRate: 20, // Set to 0 to have no update loop at all.
        currentPage: "/html/homePage.html",
        gameName: "SDK Game Sample", // Used by share contract on scores page.
    }
    ...
}

Echemos un vistazo ahora a default.js. Aquí podemos ver cómo se crea un objeto con el tipo GameState, llamado state. Más tarde, se define un espacio de nombres llamado GameManager ystate se vincula como miembro.

var state = new GameState();
...
WinJS.Namespace.define("GameManager", {
    navigateHome: navigateHome,
    navigateGame: navigateGame,
    navigateRules: navigateRules,
    navigateScores: navigateScores,
    navigateCredits: navigateCredits,
    showPreferences: showPreferences,
    onBeforeShow: onBeforeShow,
    onAfterHide: onAfterHide,
    enableAppBarGameButtons: enableAppBarGameButtons,
    disableAppBarGameButtons: disableAppBarGameButtons,
    game: game,
    state: state,
    assetManager: assetManager,
    scoreHelper: scoreHelper,
    gameId: gameId,
    frameTimer: frameTimer,
    touchPanel: touchPanel
});

Más adelante, en gamePage.js, vemos referencias a la propiedad frameRate en la colección de propiedades de config en el objeto llamado state que creamos y vinculamos a GameManager anteriormente.

if (GameManager.state.config.frameRate > 0) { ...

Esta es la técnica que permite a GameManager realizar un seguimiento de todo el estado durante todo el juego.

Administración de activos

La clase AssetManager contiene la funcionalidad que carga activos, notifica al juego que la carga se ha completado y reproduce sonidos. Agregar activos al AssetManager de tu juego es como agregar líneas a la función getAssets en game.js. Coloca tus archivos de sonido y gráficos en cualquier lugar de la jerarquía de archivos del proyecto y luego agrega una línea por cada archivo, poniéndoles un nombre simbólico.

Aquí vemos tres ejemplos que muestran un efecto sonoro (audio sin bucle), una pieza de música (audio con bucle) y un archivo de gráfico.

getAssets: function () {
    var assets = {
        sndBounce: { object: null, fileName: "/sounds/bounce.wav", fileType: AssetType.audio, loop: false },
        backgroundImage: { object: null, fileName: "/images/background.jpg", fileType: AssetType.image },
        sndMusic: { object: null, fileName: "/sounds/music", fileType: AssetType.audio, loop: true }
    };
    return assets;
}

Más adelante, en default.js, los activos se cargan.

assetManager.load(game.getAssets(), assetLoadComplete);

Como se demostró antes, todo se acumula en GameManager.

Aquí, en game.js, vemos que un objeto llamado AssetManager (que es del tipo AssetManager, por supuesto) se ha vinculado al espacio de nombres GameManager y se le ha asignado la tarea de reproducir el activo de audio sndBounce definido anteriormente.

GameManager.assetManager.playSound(GameManager.assetManager.assets.sndBounce);

Configuración de un bucle de actualización del juego y de un bucle de representación

La construcción que normalmente hace diferente a un videojuego de, por ejemplo, una aplicación de productividad en la oficina o un sitio web, es que ejecuta y actualiza su estado tanto si interactúas con él como si no. La animación, los temporizadores y el sonido siguen ejecutándose—y eso requiere que el código esté en un estado de actualización constante. Algo que permanezca quieto hasta que se haga clic en un botón puede permanecer inactivo y esperar a que se produzcan los eventos de clic antes de que se ejecute un solo código. No así en un juego y, por lo tanto, tenemos bucles de juego y de representación.

  • Bucle de actualización del juego. El bucle en tiempo real por antonomasia, donde se define la funcionalidad principal del juego. Todo objeto que va a moverse ve incrementarse su posición en 1. En cada iteración se comprueban numerosas condiciones y, si se cumple, ocurren cosas, como que un objeto cambie de dirección o se escuche un efecto sonoro. La puntuación se actualiza si se ha producido algo que lo merezca. Se comprueba la entrada del usuario. Y todo ello ocurre en un intervalo predecible, bloqueado en la velocidad de fotogramas del juego.
  • Bucle de representación. Mientras que un bucle de actualización del juego considera un sinnúmero de factores internos y externos para actualizar el estado del juego y realizar un seguimiento de lo que ocurre en un grupo de variables internas, el bucle de representación observa regularmente el estado del juego, actualizado por el bucle de actualización, para ver qué es necesario volver a dibujar en la pantalla. Un bucle de representación ha de ser capaz de perder y recuperar la sincronización con el tic de actualización. Se configurará para representar las actualizaciones que se produzcan en el estado del juego con tanta frecuencia como sea posible, pero puede omitir fotogramas si los recursos del dispositivo se vuelven escasos. El bucle de representación no está encerrado con el bucle de actualización del juego: se ejecutan en paralelo, con el bucle de actualización del juego recopilando datos para actualizar el estado del juego y el bucle de representación ejecutándose a la par, representando dicho estado de la manera más precisa que puede.

Bucle de actualización del juego

Primero analicemos el bucle de actualización del juego. Así es como se conecta. Se llama a una función reset en un objeto FrameTimer (definido en frameTimer.js) en la rutina de inicialización, pasando la función a la que se llamará cada iteración del bucle de actualización del juego y la velocidad de fotogramas (o frecuencia) con la cuál se la llamará (desde gamePage.js).

updateTimer.reset(updateLoop, GameManager.state.config.frameRate);

Esta función reset establece el temporizador del juego para que este se ejecute de acuerdo con la velocidad de fotogramas almacenada en GameManager. Ahora sabemos que se llamará a updateLoop, pero ¿qué hay allí? (desde gamePage.js)

function updateLoop() {
    if (typeof gameCanvas !== "undefined") {
        GameManager.game.update();
    }
}

La función updateLoop llama a la función update en el objeto principal del juego (desde game.js). Eso tiene mucho sentido.

No analizaremos el contenido de update porque debes personalizarlo para obtener la lógica de tu juego. Debe incrementar el estado del juego internamente en la cantidad que esperarías que lo hiciera si hubiera transcurrido un lapso de tiempo apropiado para el fotograma. Y sabemos que el objeto FrameTimer llama a la función updateLoop muchas veces por segundo, porque especificamos la velocidad de fotogramas y la pasamos a reset. Por ello, no tendrás que preocuparte acerca de la conexión de la estructura del bucle al iniciar con esta muestra.

Bucle de representación

Ahora analicemos el bucle de representación. Como especificamos antes, el bucle de representación debe iterar con la mayor frecuencia posible, no siguiendo el régimen temporal con el que se ejecuta el bucle de actualización del juego. Probablemente pienses que es difícil determinar cuándo representar un fotograma y cuándo no, pero afortunadamente, no lo es. —Esa decisión está controlada por una función llamada requestAnimationFrame. Aquí se explica cómo usamos esta función en la rutina de inicialización que inicia el bucle de representación.

GameManager.gameId = window.requestAnimationFrame(gamePage.renderLoop);

Una llamada a renderLoop, que al igual que updateLoop, está en gamePage.js. ¿Qué tiene renderLoop almacenado para nosotros?

function renderLoop() {
    if (typeof gameCanvas !== "undefined") {
        GameManager.game.draw();
        window.requestAnimationFrame(renderLoop);
    }
}

Dos instrucciones muy importantes: una llamada para que el juego dibuje un fotograma en la pantalla que muestre el estado del juego y una llamada a sí misma, una vez más pasada a través de requestAnimationFrame.

¿Por qué una llamada a sí misma? Porque el bucle de representación se configura mediante el uso de la técnica de recursión, mientras que el bucle de actualización del juego se configura mediante el uso de la técnica de un temporizador. Lo que esto significa es que "tan pronto como puedas, dibuja otro fotograma". En ese momento, se llama a draw nuevamente. Cada vez que se llama a draw, el último estado del juego se transforma en una representación visual en la pantalla. El uso de requestAnimationFrame garantiza que esto nunca suceda más de lo que se considera necesario, dado el límite superior de la frecuencia de actualización disponible en la pantalla y dados los recursos disponibles en el dispositivo.

Al igual que con el bucle de actualización del juego, no analizaremos el contenido dedraw, dado que este método también debe personalizarse conforme a tu juego.

Resumen

Si cumples con los requisitos previos, si has consultado la documentación sobre las otras convenciones de aplicaciones de la Tienda Windows que se usan en esta muestra y si has leído hasta aquí, deberías tener ideas sobre cómo usar esta muestra a tu favor y dar un impulso al desarrollo de tu juego. El control de los activos, el estado del juego, el funcionamiento del bucle de actualización y del bucle de representación, la suspensión, la reanudación y mucho más están aquí para que los utilices. Estamos impacientes por ver qué se te ocurre.

Temas relacionados

Desarrollo de juegos

Cómo dibujar en un Canvas de HTML5

Muestra de juego táctil de JavaScript y HTML5