Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Un juego web en una hora
Descargar el código de muestra
No es necesario un conjunto de conocimientos completamente nuevo para desarrollar juegos. De hecho, sus conocimientos actuales de desarrollo web en HTML, JavaScript, CSS, etc. son perfectamente válidos para una amplia variedad de juegos. Cuando compila un juego con tecnologías web, se ejecutará en prácticamente cualquier dispositivo con un explorador.
Para demostrarlo, enseñaré cómo crear un juego desde cero mediante tecnologías web y con solo dos bibliotecas externas, y lo haré en menos de una hora. Abarcaré una variedad de temas de desarrollo de juegos, desde la distribución y el diseño básicos, pasando por los controles y sprites, a la inteligencia artificial (IA) de un oponente simple. Incluso desarrollaré el juego para que funcione en equipos, tabletas y smartphones. Si tiene algo de experiencia en programación como desarrollador web u otro dominio de desarrollo, pero no tiene experiencia en la escritura de juegos, este artículo le servirá como iniciación. Si me da una hora, prometo ponerle al día.
Puesta en funcionamiento
Haré todo el desarrollo en Visual Studio, lo que permitirá una ejecución rápida de la aplicación web a medida que realizo cambios. Asegúrese de tener la versión más reciente de Visual Studio (puede descargarla en bit.ly/1xEjEnX) para que pueda seguir el artículo. He usado Visual Studio 2013 Pro, pero he actualizado el código con Visual Studio 2013 Community.
Esta aplicación no requerirá código de servidor, así que comenzaré creando un nuevo proyecto de página web vacía en Visual Studio. Usaré la plantilla vacía de C# para un sitio web, seleccionando la opción Visual C# después de seleccionar Archivo | Nuevo | Sitio web vacío de ASP.NET.
El archivo HTML index solo requiere tres recursos: jQuery, una hoja de estilos principal y un archivo principal de JavaScript. Agregaré un archivo CSS vacío al proyecto de nombre style.css y un archivo de JavaScript vacío llamado ping.js, para evitar errores al cargar la página:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
<script src="ping.js"></script>
<link rel="stylesheet" href="style.css"></script>
</head>
<body>
</body>
</html>
Diseño básico
El juego que crearé es una variante del Pong al que llamo Ping. Esencialmente, Ping tiene las mismas reglas que el Pong, salvo que los jugadores atrapan la bola cuando les llega y entonces pueden lanzarla directamente o con un ángulo hacia arriba o abajo. A menudo es mejor dibujar el aspecto que le gustaría que tuviese el juego antes de crearlo. Para este juego, el diseño general que me gustaría tener se muestra en la Figura 1.
Figura 1. El diseño general de Ping
Una vez he desarrollado el diseño del juego, es solo cuestión de agregar cada elemento en HTML para crear el juego. Sin embargo, hay que tener en cuenta que agruparé el marcador de puntuación y los controles para asegurarme de que encajan. De esta forma, verá que he agregado los elementos uno por uno, como se muestra en la Figura 2.
Figura 2. El diseño HTML inicial
<div id="arena">
<div id="score">
<h1>
<span id="playerScore">0</span>
-
<span id="opponentScore">0</span>
</h1>
</div>
<div id="player"></div>
<div id="opponent"></div>
<div id="ball"></div>
<div id="controls-left">
<div id="up"></div>
<div id="down"></div>
</div>
<div id="controls-right">
<div id="left"></div>
<div id="right"></div>
</div>
</div>
Un juego con estilo
Si cargara esta página, no vería nada porque no hay ningún estilo aplicado. Ya he configurado un vínculo a un archivo main.css en mi código HTML, por lo que voy a colocar todo mi CSS en un nuevo archivo con ese nombre. Lo primero que haré será colocar todos los elementos en la pantalla. El cuerpo de la página debe ocupar toda la pantalla, por lo que lo configuraré primero:
body {
margin: 0px;
height: 100%;
}
En segundo lugar, debo hacer que el campo llene toda la pantalla con la imagen de fondo del campo aplicada (consulte la Figura 3):
#arena {
background-image: url(arena.png);
background-size: 100% 100%;
margin: 0px;
width: 100%;
height: 100%;
overflow: hidden;
}
Figura 3. La imagen de fondo del campo
A continuación, colocaré el marcador de puntuación. Quiero que aparezca arriba y centrado, sobre los demás elementos. El comando position: absolute me permite colocarlo donde quiera y left: 50% lo coloca en la mitad de la parte superior de la ventana, pero comenzando en el extremo izquierdo del elemento del marcador de puntuación. Para asegurarme de que esté perfectamente centrado, utilizo la propiedad transform, y la propiedad z-index garantiza que siempre esté en la parte superior:
#score {
position: absolute;
z-index: 1000;
left: 50%;
top: 5%;
transform: translate(-50%, 0%);
}
También quiero que la fuente de texto tenga un estilo retro. Los exploradores más modernos permiten incluyen fuentes propias. Encontré la fuente Press Start 2P de codeman38 (zone38.net). Para agregar la fuente al marcador de puntuación, debo crear un nuevo elemento font face:
@font-face {
font-family: 'PressStart2P';
src: url('PressStart2P.woff');
}
Ahora, las puntuaciones se encuentran en una etiqueta h1, por lo que puedo configurar la fuente para todas las etiquetas h1. En caso de que la fuente no esté disponible, proporcionaré algunas opciones alternativas:
h1 {
font-family: 'PressStart2P', 'Georgia', serif;
}
Para los demás elementos, usaré una hoja de sprites de imágenes. Una hoja de sprites contiene todas las imágenes que necesito para el juego en un único archivo (consulte la Figura 4).
Figura 4. La hoja de sprites de Ping
Cualquier elemento que tenga una imagen en esta hoja tendrá una clase sprite asignada. A continuación, para cada elemento, usaré background-position para definir la parte de la hoja de sprite que quiero mostrar:
.sprite {
background-image: url("sprites.png");
width: 128px;
height: 128px;
}
A continuación, agregaré la clase sprite a todos los elementos que usarán la hoja de sprites. Tendré que volver brevemente a HTML para hacer esto:
<div id="player" class="sprite"></div>
<div id="opponent" class="sprite"></div>
<div id="ball" class="sprite"></div>
<div id="controls-left">
<div id="up" class="sprite"></div>
<div id="down" class="sprite"></div>
</div>
<div id="controls-right">
<div id="left" class="sprite"></div>
<div id="right" class="sprite"></div>
</div>
Ahora tengo que indicar las posiciones de cada sprite en la hoja para cada elemento. De nuevo, lo haré con background-position, como se muestra en la Figura 5.
Figura 5. Incorporación de desplazamientos de la hoja de sprites
#player {
position: absolute;
background-position: 0px 128px;
}
#opponent {
position: absolute;
background-position: 0px 0px;
}
#ball {
position: absolute;
background-position: 128px 128px;
}
#right {
background-position: 64px 192px;
}
#left {
background-position: 64px 0px;
}
#down {
background-position: 128px 192px;
}
#up {
background-position: 128px 0px;
}
La propiedad position: absolute en el jugador, el oponente y la bola me permitirá moverlos con JavaScript. Si observa la página ahora, podrá ver que los controles y la bola tienen partes innecesarias vinculadas a ellos. Esto es debido a que los tamaños de sprite son menores que los 128 píxeles predeterminados, por lo que deberá ajustarlos al tamaño correcto. Solo hay una bola, por lo que configuraré su tamaño directamente:
#ball {
position: absolute;
width: 64px;
height: 64px;
background-position: 128px 128px;
}
Hay cuatro elementos de control (botones que el usuario puede presionar para mover el jugador), por lo que me corresponde crear una clase especial para ellos. También agregaré un margen de modo que tengan poco espacio alrededor:
.control {
margin: 16px;
width: 64px;
height: 64px;
}
Después de agregar esta clase, el juego tiene unos controles mucho más vistosos:
<div id="controls-left">
<div id="up" class="sprite control"></div>
<div id="down" class="sprite control"></div>
</div>
<div id="controls-right">
<div id="left" class="sprite control"></div>
<div id="right" class="sprite control"></div>
</div>
Lo último que debo hacer es colocar los controles de forma que queden de forma adecuada para los pulgares cuando la página se ejecute en un dispositivo móvil. Los ubicaré en las esquinas de la parte inferior:
#controls-left {
position: absolute;
left: 0; bottom: 0;
}
#controls-right {
position: absolute;
right: 0; bottom: 0;
}
Una ventaja de este diseño es que todo se establece con posiciones relativas. Esto significa que la pantalla puede tener distintos tamaños y el juego se seguirá viendo bien.
Siguiendo la bola que rebota
Ahora haré que la bola se mueva. Para el código JavaScript, he hecho referencia a un archivo denominado ping.js en HTML, al igual que hice con el CSS. Agregaré este código a un nuevo archivo con ese nombre. Crearé objetos para la bola y para cada uno de los jugadores, pero usaré el patrón de fábrica para los objetos.
Este es un concepto simple. La función Ball crea una nueva bola al llamarla. No hay necesidad de utilizar palabra clave new. Este patrón reduce la confusión en torno a esta variable al clarificar las propiedades de objeto disponibles. Y dado que solo tengo una hora para crear este juego, debo minimizar los conceptos confusos.
La estructura de este patrón, mientras creo la clase simple Ball, se muestra en la Figura 6.
Figura 6. La clase Ball
var Ball = function( {
// List of variables only the object can see (private variables).
var velocity = [0,0];
var position = [0,0];
var element = $('#ball');
var paused = false;
// Method that moves the ball based on its velocity. This method is only used
// internally and will not be made accessible outside of the object.
function move(t) {
}
// Update the state of the ball, which for now just checks
// if the play is paused and moves the ball if it is not.
// This function will be provided as a method on the object.
function update(t) {
// First the motion of the ball is handled
if(!paused) {
move(t);
}
}
// Pause the ball motion.
function pause() {
paused = true;
}
// Start the ball motion.
function start() {
paused = false;
}
// Now explicitly set what consumers of the Ball object can use.
// Right now this will just be the ability to update the state of the ball,
// and start and stop the motion of the ball.
return {
update: update,
pause: pause,
start: start
}
Para crear una bola nueva, simplemente llamo a esta función que he definido:
var ball = Ball();
Ahora haré que la bola se mueva y rebote por la pantalla. En primer lugar, es necesario llamar a la función de actualización cada cierto tiempo para crear una animación de la bola. Los exploradores modernos proporcionan una función diseñada para este propósito que se denomina requestAnimationFrame. Toma una función como argumento y llamará a esa función la próxima vez que se ejecute el ciclo de animación. Esto permite que la bola se mueva en pasos suaves cuando el explorador esté listo para una actualización. Cuando llama a la función que se le ha pasado, le asignará el tiempo en segundos desde que se cargó la página. Esto es fundamental para garantizar que las animaciones son coherentes en el tiempo. En el juego, el uso de requestAnimationFrame aparece como sigue:
var lastUpdate = 0;
var ball = Ball();
function update(time) {
var t = time - lastUpdate;
lastUpdate = time;
ball.update(t);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
Tenga en cuenta que se llama de nuevo a requestAnimationFrame en la función, cuando la bola ha terminado la actualización. Esto garantiza una animación continua.
Aunque este código funcionará, puede haber un problema si el script comienza a ejecutarse antes de que la página esté completamente cargada. Para evitar esto, iniciaré el código cuando la página esté cargada, mediante jQuery:
var ball;
var lastUpdate;
$(document).ready(function() {
lastUpdate = 0;
ball = Ball();
requestAnimationFrame(update);
});
Puesto que conozco la velocidad de la bola (velocity) y el tiempo transcurrido desde la última actualización, puedo crear una física simple que mueva la bola hacia delante:
var position = [300, 300];
var velocity = [-1, -1];
var move = function(t) {
position[0] += velocity[0] * t;
position[1] += velocity[1] * t;
element.css('left', position[0] + 'px');
element.css('top', position[1] + 'px');
}
Intente ejecutar el código y verá que la bola se mueve en un ángulo y hacia fuera de la pantalla. Esto es divertido durante un segundo, pero cuando la bola se sale del borde de la pantalla, se acaba la diversión. Por esto, el paso siguiente consiste en conseguir que la bola rebote en los bordes de la pantalla, tal y como se implementa en la Figura 7. Si se agrega este código y se ejecuta la aplicación, se mostrará una bola que rebota continuamente.
Figura 7. Física simple de rebote de la bola
var move = function(t) {
// If the ball hit the top or bottom, reverse the vertical speed.
if (position[1] <= 0 || position[1] >= innerHeight) {
velocity[1] = -velocity[1];
}
// If the ball hit the left or right sides, reverse the horizontal speed.
if (position[0] <= 0 || position[0] >= innerWidth) {
velocity[0] = -velocity[0];
}
position[0] += velocity[0] * t;
position[1] += velocity[1] * t;
element.css('left', (position[0] - 32) + 'px');
element.css('top', (position[1] - 32) + 'px');
}
Un jugador que se mueva
Ahora es momento de crear los objetos Player. El primer paso para dar cuerpo a la clase player será hacer que la función move cambie la posición del jugador. La variable side indicará el lado del campo en el que estará el jugador, que determinará cómo se posicionará el jugador horizontalmente. El valor y, que se pasa a la función move, indicará cuánto se moverá hacia arriba o abajo el jugador:
var Player = function (elementName, side) {
var position = [0,0];
var element = $('#'+elementName);
var move = function(y) {
}
return {
move: move,
getSide: function() { return side; },
getPosition: function() { return position; }
}
}
La Figura 8 expone el movimiento del jugador, deteniendo el movimiento si el sprite del jugador alcanza la parte superior o inferior de la ventana.
Ahora puedo crear dos jugadores y hacer que se muevan a su lado correspondiente de la pantalla:
player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);
Figura 8. Los controles de movimiento del sprite del jugador
var move = function(y) {
// Adjust the player's position.
position[1] += y;
// If the player is off the edge of the screen, move it back.
if (position[1] <= 0) {
position[1] = 0;
}
// The height of the player is 128 pixels, so stop it before any
// part of the player extends off the screen.
if (position[1] >= innerHeight - 128) {
position[1] = innerHeight - 128;
}
// If the player is meant to stick to the right side, set the player position
// to the right edge of the screen.
if (side == 'right') {
position[0] = innerWidth - 128;
}
// Finally, update the player's position on the page.
element.css('left', position[0] + 'px');
element.css('top', position[1] + 'px');
}
Entrada de teclado
En teoría puede mover el jugador, pero no se moverá sin una instrucción. Agregue algunos controles para el jugador a la izquierda. Querrá controlar a ese jugador de dos formas: mediante el teclado (en equipos) y tocando los controles (en tabletas y teléfonos).
Para garantizar la coherencia entre las entradas táctiles y de mouse en distintas plataformas, usaré el magnífico marco unificador Hand.js (handjs.codeplex.com). En primer lugar, agregaré el script al HTML en la sección head:
<script src="hand.minified-1.3.8.js"></script>
En la Figura 9 se muestra cómo utilizar Hand.js y jQuery para controlar el jugador cuando se presionan las teclas A y Z, o al tocar los controles.
Figura 9. Incorporación de los controles táctiles y de teclado
var distance = 24; // The amount to move the player each step.
$(document).ready(function() {
lastUpdate = 0;
player = Player('player', 'left');
player.move(0);
opponent = Player('opponent', 'right');
opponent.move(0);
ball = Ball();
// pointerdown is the universal event for all types of pointers -- a finger,
// a mouse, a stylus and so on.
$('#up') .bind("pointerdown", function() {player.move(-distance);});
$('#down') .bind("pointerdown", function() {player.move(distance);});
requestAnimationFrame(update);
});
$(document).keydown(function(event) {
var event = event || window.event;
// This code converts the keyCode (a number) from the event to an uppercase
// letter to make the switch statement easier to read.
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'A':
player.move(-distance);
break;
case 'Z':
player.move(distance);
break;
}
return false;
});
Atrapar la bola
Como la bola rebota, quiero que los jugadores puedan atraparla. Cuando se atrapa, la bola tiene un propietario y sigue el movimiento de ese propietario. La Figura 10 agrega funcionalidad al método move de la bola, lo que permite un propietario al que la bola debe seguir.
Figura 10. Hacer que la bola siga a su propietario
var move = function(t) {
// If there is an owner, move the ball to match the owner's position.
if (owner !== undefined) {
var ownerPosition = owner.getPosition();
position[1] = ownerPosition[1] + 64;
if (owner.getSide() == 'left') {
position[0] = ownerPosition[0] + 64;
} else {
position[0] = ownerPosition[0];
}
// Otherwise, move the ball using physics. Note the horizontal bouncing
// has been removed -- ball should pass by a player if it
// isn't caught.
} else {
// If the ball hits the top or bottom, reverse the vertical speed.
if (position[1] - 32 <= 0 || position[1] + 32 >= innerHeight) {
velocity[1] = -velocity[1];
}
position[0] += velocity[0] * t;
position[1] += velocity[1] * t;
}
element.css('left', (position[0] - 32) + 'px');
element.css('top', (position[1] - 32) + 'px');
}
Actualmente, no hay manera de obtener la posición de un objeto Player, por lo que agregaré los descriptores de acceso getPosition y getSide para el objeto Player:
return {
move: move,
getSide: function() { return side; },
getPosition: function() { return position; }
}
Ahora, si la bola tiene un propietario, seguirá a ese propietario. Pero, ¿cómo se determina el propietario? Alguien tiene que atrapar la bola. En la Figura 11 se muestra cómo determinar si uno de los sprites de jugador toca la bola. Cuando eso ocurre, estableceré que el propietario de la bola es ese jugador.
Figura 11. Detección de colisiones de la bola y los jugadores
var update = function(t) {
// First the motion of the ball is handled.
if(!paused) {
move(t);
}
// The ball is under control of a player, no need to update.
if (owner !== undefined) {
return;
}
// First, check if the ball is about to be grabbed by the player.
var playerPosition = player.getPosition();
if (position[0] <= 128 &&
position[1] >= playerPosition[1] &&
position[1] <= playerPosition[1] + 128) {
console.log("Grabbed by player!");
owner = player;
}
// Then the opponent...
var opponentPosition = opponent.getPosition();
if (position[0] >= innerWidth - 128 &&
position[1] >= opponentPosition[1] &&
position[1] <= opponentPosition[1] + 128) {
console.log("Grabbed by opponent!");
owner = opponent;
}
Si prueba a jugar ahora, comprobará que la bola rebota contra la parte superior de la pantalla y que puede mover el jugador para atraparla. Y ahora, ¿cómo se lanza? Para eso son los controles de la mano derecha: para apuntar con la bola. La Figura 12 agrega una función "fire" a player, así como una propiedad aim.
Figura 12 Apuntar y lanzar la bola
var aim = 0;
var fire = function() {
// Safety check: if the ball doesn't have an owner, don't not mess with it.
if (ball.getOwner() !== this) {
return;
}
var v = [0,0];
// Depending on the side the player is on, different directions will be thrown.
// The ball should move at the same speed, regardless of direction --
// with some math you can determine that moving .707 pixels on the
// x and y directions is the same speed as moving one pixel in just one direction.
if (side == 'left') {
switch(aim) {
case -1:
v = [.707, -.707];
break;
case 0:
v = [1,0];
break;
case 1:
v = [.707, .707];
}
} else {
switch(aim) {
case -1:
v = [-.707, -.707];
break;
case 0:
v = [-1,0];
break;
case 1:
v = [-.707, .707];
}
}
ball.setVelocity(v);
// Release control of the ball.
ball.setOwner(undefined);
}
// The rest of the Ball definition code goes here...
return {
move: move,
fire: fire,
getSide: function() { return side; },
setAim: function(a) { aim = a; },
getPosition: function() { return position; },
}
La Figura 13 aumenta la función de teclado para establecer las funciones aim y fire de player. El funcionamiento para apuntar es ligeramente distinto. Cuando se libera la tecla para apuntar, se volverá a apuntar en dirección recta.
Figura 13. Establecimiento de la función para apuntar del jugador
$(document).keydown(function(event) {
var event = event || window.event;
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'A':
player.move(-distance);
break;
case 'Z':
player.move(distance);
break;
case 'K':
player.setAim(-1);
break;
case 'M':
player.setAim(1);
break;
case ' ':
player.fire();
break;
}
return false;
});
$(document).keyup(function(event) {
var event = event || window.event;
switch(String.fromCharCode(event.keyCode).toUpperCase()) {
case 'K':
case 'M':
player.setAim(0);
break;
}
return false;
});
La última incorporación será la compatibilidad táctil para todos los controles. Haré que los controles de la parte derecha cambien el elemento aim de player. También haré que al tocar en cualquier parte en la pantalla se lance la bola:
$('#left') .bind("pointerdown", function() {player.setAim(-1);});
$('#right') .bind("pointerdown", function() {player.setAim(1);});
$('#left') .bind("pointerup", function() {player.setAim(0);});
$('#right') .bind("pointerup", function() {player.setAim(0);});
$('body') .bind("pointerdown", function() {player.fire();});
Mantener la puntuación
Cuando la bola sobrepase a un jugador, quiero cambiar la puntuación y asignar la bola a ese jugador. Usaré eventos personalizados para poder separar la puntuación desde cualquiera de los objetos existentes. La función update está quedando larga, por lo que agregaré una nueva función privada denominada checkScored:
function checkScored() {
if (position[0] <= 0) {
pause();
$(document).trigger('ping:opponentScored');
}
if (position[0] >= innerWidth) {
pause();
$(document).trigger('ping:playerScored');
}
}
En la Figura 14 se muestra el código que reacciona a esos eventos para actualizar la puntuación y entregar la bola. Agregue este código a la parte inferior del documento de JavaScript.
Figura 14. Actualización del marcador de puntuación
$(document).on('ping:playerScored', function(e) {
console.log('player scored!');
score[0]++;
$('#playerScore').text(score[0]);
ball.setOwner(opponent);
ball.start();
});
$(document).on('ping:opponentScored', function(e) {
console.log('opponent scored!');
score[1]++;
$('#opponentScore').text(score[1]);
ball.setOwner(player);
ball.start();
});
Ahora, cuando la bola supere al adversario (lo que no es tan difícil, ya que el oponente no se mueve), se aumentará su puntuación y la bola se entregará al oponente. Sin embargo, el oponente simplemente mantendrá la bola.
Un juego inteligente
Ya casi tiene un juego. Solo falta una persona con la que jugar. Como el último paso, le mostraré cómo controlar el oponente con IA simple. El oponente intentará mantenerse paralelo con la bola cuando se desplaza. Si el adversario atrapa la bola, se moverá aleatoriamente y la lanzará en una dirección aleatoria. Para que la IA se sienta un poco más humana, agregaré retrasos a todo lo que haga. No se trata de una IA muy inteligente, pero dará la posibilidad de jugar contra alguien.
Al diseñar este tipo de sistema, es conveniente pensar en estados. La IA del oponente tiene tres estados posibles: siguiendo, apuntando/lanzando y esperando. Yo seré el estado entre las acciones siguientes para agregar un elemento más humano. Comience solo con eso para el objeto de IA:
function AI(playerToControl) {
var ctl = playerToControl;
var State = {
WAITING: 0,
FOLLOWING: 1,
AIMING: 2
}
var currentState = State.FOLLOWING;
}
Según el estado de la IA, querré realizar una acción diferente. Igual que con ball, crearé una función update a la que podré llamar en requestAnimationFrame para que la IA actúe según su estado:
function update() {
switch (currentState) {
case State.FOLLOWING:
// Do something to follow the ball.
break;
case State.WAITING:
// Do something to wait.
break;
case State.AIMING:
// Do something to aim.
break;
}
}
El estado FOLLOWING es bastante sencillo. El oponente se mueve en la dirección vertical de la bola y la IA hace una transición al estado WAITING para insertar un tiempo de reacción algo ralentizado. En la Figura 15 se muestran estos dos estados.
Figura 15. Una IA simple de FOLLOWING
function moveTowardsBall() {
// Move the same distance the player would move, to make it fair.
if(ball.getPosition()[1] >= ctl.getPosition()[1] + 64) {
ctl.move(distance);
} else {
ctl.move(-distance);
}
}
function update() {
switch (currentState) {
case State.FOLLOWING:
moveTowardsBall();
currentState = State.WAITING;
case State.WAITING:
setTimeout(function() {
currentState = State.FOLLOWING;
}, 400);
break;
}
}
}
Con el código de la Figura 15, la IA cambia entre tener que seguir la bola y esperar un instante. Ahora, agregue el código a la función update de todo el juego:
function update(time) {
var t = time - lastUpdate;
lastUpdate = time;
ball.update(t);
ai.update();
requestAnimationFrame(update);
}
Al ejecutar el juego, verá que el oponente sigue los movimientos de la bola. No está nada mal para una IA de menos de 30 líneas de código. Por supuesto, si el oponente atrapa la bola, no hará nada. Así que, como un retoque de última hora, es el momento de controlar las acciones del estado AIMING. Quiero que la IA se mueva aleatoriamente varias veces y, a continuación, lance la bola en una dirección aleatoria. La Figura 16 agrega una función privada que se encarga de hacerlo. Al agregar la función aimAndFire a la instrucción case AIMING, consigue una IA completamente funcional con la que puede jugar.
Figura 16. Una IA que apunta y lanza
function repeat(cb, cbFinal, interval, count) {
var timeout = function() {
repeat(cb, cbFinal, interval, count-1);
}
if (count <= 0) {
cbFinal();
} else {
cb();
setTimeout(function() {
repeat(cb, cbFinal, interval, count-1);
}, interval);
}
}
function aimAndFire() {
// Repeat the motion action 5 to 10 times.
var numRepeats = Math.floor(5 + Math.random() * 5);
function randomMove() {
if (Math.random() > .5) {
ctl.move(-distance);
} else {
ctl.move(distance);
}
}
function randomAimAndFire() {
var d = Math.floor( Math.random() * 3 - 1 );
opponent.setAim(d);
opponent.fire();
// Finally, set the state to FOLLOWING.
currentState = State.FOLLOWING;
}
repeat(randomMove, randomAimAndFire, 250, numRepeats);
}
Resumen
En este momento, ya dispone de un juego web completo que funciona en equipos, smartphones y tabletas. Hay muchas mejoras posibles para este juego. Por ejemplo, tendrá un aspecto un poco extraño en el modo vertical de un smartphone, por lo que debe asegurarse de que mantiene el teléfono en posición horizontal para que funcione correctamente. Esto es solo una pequeña demostración de las posibilidades de desarrollo de juegos para web y más allá.
Michael Oneppo *es un especialista en tecnología creativa que fue jefe de programas en el equipo de Direct3D de Microsoft. Entre sus esfuerzos recientes se incluye su trabajo como director de tecnología en la tecnología sin ánimo de lucro Library For All y la investigación en un máster en el NYU Interactive Telecommunications Program.*
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Mohamed Ameen Ibrahim