Ya vimos como hacer para que un objeto interactúe con otro. El problema sería si quisiéramos, por ejemplo, querer interactuar con 50 elementos que hagan exactamente lo mismo (Como serían por ejemplo los enemigos). Tener que evaluar uno por uno sería demasiado tedioso y complicado. Afortunadamente, hay una forma más sencilla de interactuar con varios elementos de propiedades iguales a través de los arreglos.
Para este ejemplo, crearemos una variable de tipo arreglo llamada “wall”:
Arreglemos esto. Comencemos por saber cuando el jugador ha perdido, a través de una variable llamada “gameover”:
Para este ejemplo, crearemos una variable de tipo arreglo llamada “wall”:
var wall = new Array();
Este arreglo contendrá todos nuestros elementos de tipo pared. Ahora, agregaremos cuatro elementos a este arreglo en la función “init” de la siguiente forma: // Create walls
wall.push(new Rectangle(100, 50, 10, 10));
wall.push(new Rectangle(100, 100, 10, 10));
wall.push(new Rectangle(200, 50, 10, 10));
wall.push(new Rectangle(200, 100, 10, 10));
Para dibujar los elementos de la pared, recorreremos los elementos del arreglo a través de un “for”, de la siguiente forma: // Draw walls
ctx.fillStyle = '#999';
for (i = 0, l = wall.length; i < l; i += 1) {
wall[i].fill(ctx);
}
De igual forma, comprobaremos cada elemento de la pared con un “for”, y comprobaremos si hace una intersección con la comida o el jugador: // Wall Intersects
for (i = 0, l = wall.length; i < l; i += 1) {
if (food.intersects(wall[i])) {
food.x = random(canvas.width / 10 - 1) * 10;
food.y = random(canvas.height / 10 - 1) * 10;
}
if (player.intersects(wall[i])) {
pause = true;
}
}
Primero, comprobamos si la comida choca con la pared. En dado caso, cambiamos de lugar la comida, esto evitará que esta quede “atorada” en la pared. Segundo, comprobamos si el jugador choca con la pared, y en tal caso, el juego se detendrá. Eso está bien, pero lo ideal sería que cuando el jugador choque, al reanudar el juego, este comience desde el principio.Arreglemos esto. Comencemos por saber cuando el jugador ha perdido, a través de una variable llamada “gameover”:
var gameover = true;
Luego, agreguemos estas líneas justo donde el juego comienza, después del “if (!pause)”: // GameOver Reset
if (gameover) {
reset();
}
De esta forma, llamaremos a una función llamada “reset”, donde indicaremos como queremos que inicie el juego. En este caso, pondremos el score en cero, la dirección hacia su punto original, regresaremos al jugador a su punto inicial y cambiaremos de lugar la comida. Por último, por supuesto, nos aseguraremos que el juego deje de estar en Game Over:function reset() {
score = 0;
dir = 1;
player.x = 40;
player.y = 40;
food.x = random(canvas.width / 10 - 1) * 10;
food.y = random(canvas.height / 10 - 1) * 10;
gameover = false;
}
Por último, cambiaremos el “if (pause)” en nuestra función “paint” para ver si el juego está en Game Over o en una pausa común: // Draw pause
if (pause) {
ctx.textAlign = 'center';
if (gameover) {
ctx.fillText('GAME OVER', 150, 75);
} else {
ctx.fillText('PAUSE', 150, 75);
}
ctx.textAlign = 'left';
}
De esta forma, concluimos con todos los conocimientos básicos para crear un juego. En el último capítulo, nos enfocaremos en los detalles para darle forma a este juego.Código final:
var KEY_ENTER = 13,
KEY_LEFT = 37,
KEY_UP = 38,
KEY_RIGHT = 39,
KEY_DOWN = 40,
canvas = null,
ctx = null,
lastPress = null,
pause = true,
gameover = true,
dir = 0,
score = 0,
wall = new Array(),
player = null,
food = null;
window.requestAnimationFrame = (function () {
return window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 17);
};
}());
document.addEventListener('keydown', function (evt) {
lastPress = evt.which;
}, false);
function Rectangle(x, y, width, height) {
this.x = (x == null) ? 0 : x;
this.y = (y == null) ? 0 : y;
this.width = (width == null) ? 0 : width;
this.height = (height == null) ? this.width : height;
this.intersects = function (rect) {
if (rect == null) {
window.console.warn('Missing parameters on function intersects');
} else {
return (this.x < rect.x + rect.width &&
this.x + this.width > rect.x &&
this.y < rect.y + rect.height &&
this.y + this.height > rect.y);
}
};
this.fill = function (ctx) {
if (ctx == null) {
window.console.warn('Missing parameters on function fill');
} else {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
};
}
function random(max) {
return Math.floor(Math.random() * max);
}
function reset() {
score = 0;
dir = 1;
player.x = 40;
player.y = 40;
food.x = random(canvas.width / 10 - 1) * 10;
food.y = random(canvas.height / 10 - 1) * 10;
gameover = false;
}
function paint(ctx) {
var i = 0,
l = 0;
// Clean canvas
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw player
ctx.fillStyle = '#0f0';
player.fill(ctx);
// Draw walls
ctx.fillStyle = '#999';
for (i = 0, l = wall.length; i < l; i += 1) {
wall[i].fill(ctx);
}
// Draw food
ctx.fillStyle = '#f00';
food.fill(ctx);
// Debug last key pressed
ctx.fillStyle = '#fff';
//ctx.fillText('Last Press: '+lastPress,0,20);
// Draw score
ctx.fillText('Score: ' + score, 0, 10);
// Draw pause
if (pause) {
ctx.textAlign = 'center';
if (gameover) {
ctx.fillText('GAME OVER', 150, 75);
} else {
ctx.fillText('PAUSE', 150, 75);
}
ctx.textAlign = 'left';
}
}
function act() {
var i,
l;
if (!pause) {
// GameOver Reset
if (gameover) {
reset();
}
// Change Direction
if (lastPress == KEY_UP) {
dir = 0;
}
if (lastPress == KEY_RIGHT) {
dir = 1;
}
if (lastPress == KEY_DOWN) {
dir = 2;
}
if (lastPress == KEY_LEFT) {
dir = 3;
}
// Move Rect
if (dir == 0) {
player.y -= 10;
}
if (dir == 1) {
player.x += 10;
}
if (dir == 2) {
player.y += 10;
}
if (dir == 3) {
player.x -= 10;
}
// Out Screen
if (player.x > canvas.width) {
player.x = 0;
}
if (player.y > canvas.height) {
player.y = 0;
}
if (player.x < 0) {
player.x = canvas.width;
}
if (player.y < 0) {
player.y = canvas.height;
}
// Food Intersects
if (player.intersects(food)) {
score += 1;
food.x = random(canvas.width / 10 - 1) * 10;
food.y = random(canvas.height / 10 - 1) * 10;
}
// Wall Intersects
for (i = 0, l = wall.length; i < l; i += 1) {
if (food.intersects(wall[i])) {
food.x = random(canvas.width / 10 - 1) * 10;
food.y = random(canvas.height / 10 - 1) * 10;
}
if (player.intersects(wall[i])) {
gameover = true;
pause = true;
}
}
}
// Pause/Unpause
if (lastPress == KEY_ENTER) {
pause = !pause;
lastPress = null;
}
}
function repaint() {
window.requestAnimationFrame(repaint);
paint(ctx);
}
function run() {
setTimeout(run, 50);
act();
}
function init() {
// Get canvas and context
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
// Create player and food
player = new Rectangle(40, 40, 10, 10);
food = new Rectangle(80, 80, 10, 10);
// Create walls
wall.push(new Rectangle(100, 50, 10, 10));
wall.push(new Rectangle(100, 100, 10, 10));
wall.push(new Rectangle(200, 50, 10, 10));
wall.push(new Rectangle(200, 100, 10, 10));
// Start game
run();
repaint();
}
window.addEventListener('load', init, false);
Siguiente parte: Imágenes y sonidos.
exelente sitio
ResponderBorrarHe venido siguiendo cada uno de los capitulos del tutorial, es excelente encontrar un tutorial de esta calidad, el lenguaje es claro y facil de entender. Espero siga avanzando en las entregas, estare muy pendiente para devorar todo este material. Leonardo E.
ResponderBorrarMuy buenos tutoriales, no sólo esán bien explicados sino que además motivan a seguir avanzando para acabarlos.
ResponderBorrarEn serio muchas gracias, este curso lo estoy cogiendo con ganas... cada vez me veo más cerca de poder hacer mi juego de cartas cargando imágenes con links y sin que el jugador deba bajarse nada!
ResponderBorrarSólo tengo una duda, a lo mejor ves esto y me puedes responder: se me hace raro que las funciones sean las clases, pero me acostumbraré... al principio del código haces un llamado a init cuando la página de haya cargado pero... eso no significa que, al llamar al init antes de declarar las variables, se saltaría todas las declaraciones de variables? No habría que declararlas antes y luego llamar al init? Podrías explicarme un poco el tema este del orden del init y las declaraciones, y también el orden de las funciones: importa si primero declaro una función (random por ejemplo) y luego la uso en la siguiente, o si primero la uso y luego la declaro? Podría dar error en este caso porque la primera no encuentre la segunda (pues ha sido creada después) o da igual el orden en que declaremos las funciones?
¡Ah! Pasar de C++ a JavaScript, que gran salto tan enorme y aterrador, aun recuerdo cuando entré en estado de shock cuando tuve que botar por el inodoro todo el conocimiento que me habían hecho aprender a fuerza de errores de compilación en la escuela.
BorrarTe lo resumiré así: Los verdaderos programadores, llaman a JavaScript un chiste, una broma de mal gusto, ya que es demasiado permisible y flexible. No le importa el órden de las funciones, no importa si no declaras una variable antes de usarla. Incluso, ¡No importa si olvidas poner un punto y coma al final de un renglón!
Pero siempre es mejor hacer uso de las buenas prácticas de programador, para evitar posibles errores, y para no olvidar lo aprendido cuando uno use otros lenguajes.
Por último, notando que si altera un poco esa primer línea, te explicaré como funciona la llamada al init. La función Init es agregada a un "escucha", que accionará dicha función hasta que termine de cargar la página web. Por eso las variables son declaradas antes que el propio init, y esa función se realiza hasta varios milisegundos después, cuando la página ha cargado por completo, por lo que no hay problemas con ello.
Sin embargo, es importante asignarla al momento que se cargue la página y no antes, ya que si se le llamara de forma directa [ init(); ], no solo causaría un error por iniciar antes de declarar las variables, si no que además, sería llamada antes de crearse el elemento donde se dibujaría el juego, lo que ocasionaría un grave error al intentarlo asignarlo a la variable del mismo nombre, imposibilitando el funcionamiento del juego mismo.
Buenas observaciones, que bueno que has preguntado, y espero esto tranquilice tus códigos posteriores con JavaScript. Suerte :)
ya veo, primero la página se carga entera, lo que implica cargar todas las variables declaradas, y una vez hecho todo esto llamamos al init e inicia todo, pues hasta no llamar al init nada inicia pues todo está dentro de funciones, y eso no se inicia solo!
BorrarPara organizarme mejor estoy haciendo mil funciones para todo, para poder esconder cosas. Por ejemplo, todas las declaraciones de variables con sus valores iniciales las tengo dentro de un "if(true) {..."; para poder esconderlas y trabajar más a gusto con todo lo demás (notepad++ permite esconder los ifs con un boton - que sale al lado).
Pues sí, esto de que las variables no tengan tipo (int, char, string), que las clases sean funciones... está todo muy suelto y raro. En parte no me molesta tanto, pues antes de aprender C++ aprendí GML, el lenguaje de Game Maker (estupendo programar para hacer juegos, más sencillo que javascript pues te lo da todo mascado, pero menos compatible (nada compatible) con linux, este es mas versatil) y allí tampoco dabamos tipo a las variables, ni poniamos ; si no querias, ni () rodeando los ifs... pero no era TAN flexible como javascript, mola xD
En todo caso como dices, hay que programar con normas propias. AUNQUE esto de que javascript no reporte errores por casi nada puede ser malo, pues los errores a la mínima suele ser lo que permite al programador darse cuenta de sus errores, no? Seguramente me tope con esto pronto.
Es cierto, puede ser un problema que JavaScript no reporte errores para los programadores, pero permite que códigos parcialmente correctos se ejecuten "sin problemas" para los usuarios, aunque a veces no hacen exactamente lo que el programador esperaba (Pero al menos mantiene satisfecho a los usuarios).
BorrarPor supuesto, para los programadores, tenemos buenas herramientas que es bueno siempre usar para prevenir estos problemas. En la misma introducción lo menciono, en el tercer párrafo, que cito a continuación:
"... a diferencia de muchos otros lenguajes, es imposible saber de forma nativa si existe algún problema con nuestro código de Javascript, pero hoy día los navegadores cuentan con muchas herramientas para ayudarnos con este problema. En Firefox podemos agregar el Add-On de Firebug (addons.mozilla.org/es-es/firefox/addon/firebug), el cual nos notificará de forma automática de errores en nuestro código. En Google Chrome, podemos acceder a la consola de javascript presionando F12, en la cual podremos ver los errores en nuestro código."
Espero esto te sirva para prevenir errores que puedas pasar por alto :)
Yo ahora mismo estoy cursando el 2º año de ciclo superior de Desarrollo de aplicaciones webs y ya te digo que me he pegado muchos mareos aprendiendo JavaScript exactamente por que no te dice los Errores jajaja.
ResponderBorrarReferente a tu tutorial, es muy bueno, yo aún no he dado canvas y gracias a ti me estoy iniciando en ello. Muchas gracias =).
Podrias plantearte hacer un minitutorial sobre tratamiento de los tiles, como trabajar con ellos, o incluso como crear mapas a partir de una matriz de caracteres y a continuación asignandoles tiles.
Es una idea, ya que es un tema muy interesante y al menos a mi me resultaria muy util.
Lo dicho, sigue asi y gracias por tu esfuerzo =)
Como ya he mencionado antes, usar Firebug para Firefox o la consola de JavaScript para Chrome, y no tendrás que preocuparte mas por saber si hay errores, y que los provocan.
BorrarMe alegra saber que este curso es de ayuda para ti. Sobre los tiles, ya esta planeado en un futuro, pero adelantaré el tema para que pueda ayudarte pronto. ¡Gracias!
Muchas gracias por el curso que están realizando... he aprendido aquí bastante... :)
ResponderBorrarespero sigan haciendo muchos mas de HTML5.
Hola como estas, primero que nada déjame felicitarse por tan buen material, en 12 horas ya tengo un juego funcional basándome en tus artículos... pero este juego tiene varios errores o bugs que se presentan 1 de 20 veces...
ResponderBorrarLink del juego: http://snake.angelkurten.com/
Error 1: En ocaciones la culebra pasa por encima de la comida como si nada... y al volver a pasar si se la come
Error 2: Muchaces veces cuando pierdo la todas vidas y vuelvo a empezar(sin recargar la pagina), los obstaculos no se pintan.
y por ultimo una pregunta... yo quiero que mi culebra dispare pero quiero que esta lo haga en dirección a donde tiene la cabeza... y el disparo conserve su trayectoria en caso de que la culebra cambie de ruta
Espero tu colaboración y muchas gracias.
Reviso con detalle los errores que me indicas, que si veo un poco difícil de seguir tu código a primera vista.
BorrarCon respecto a los disparos, en el siguiente tema muestro como crear disparo. Solo debes agregar un "dir" a cada rectángulo, para que así cada disparo tenga su propia dirección (Que será la misma que aquella de la cabeza de la serpiente al ser creado), y mueves el disparo de acuerdo a su respectiva dirección, de igual forma que ahora se hace con la cabeza. Espero me haya explicado claro.
¡Éxito en tu código! ;)
Excelente blog ... :)
ResponderBorrarCada momento esta mejor mi juego.
ResponderBorrarMuy bueno el tutorial, realmente muy interesante! jajajaja
ResponderBorrarestoy trabajando con Node.js, y me esta costando mas que nada la parte gráfica, esto me ayuda mucho,
BorrarEste comentario ha sido eliminado por el autor.
ResponderBorrarMuchas gracias por compartir esto.
ResponderBorrarEn las lineas que son así
ResponderBorrarfor (i = 0, l = wall.length; i < l; i += 1) {
tengo una corrección, que seguramente es de tipeo.
La forma correcta de escribirlo es
for (i = 0; l = wall.length, i < l; i += 1) {
Cambié ';' por ',' y viceversa. Sino no funciona.
Saludos y muchas gracias por compartir tu conocimiemto.
Tambien tengo para sugerir que se puede escribir directamente
Borrarfor (i = 0; i<wall.length; i += 1) {
aunque supongo que lo hicieron de la otra forma para que esté mas claro.
Muchas gracias por tu comentario. Me extraña que no te funcione en la forma que lo he hecho yo, ya que lo he hecho así durante mucho tiempo y no he tenido conflicto con ello.
BorrarLa razón por la que se hace de esta forma, es para optimizar el ciclo, ya que de esta forma sólo se lee la longitud del arreglo una vez al comienzo, mientras que de lo contrario, esta es leída en cada proceso del ciclo, lo cual puede impactar el rendimiento en arreglos de gran longitud.
La forma en que tú lo has hecho, aunque funcione, hace exactamente el mismo proceso de leer de nuevo la longitud del arreglo en cada ciclo, por lo que no resulta dicha optimización, aunque no se cual pueda ser el problema que hace que la forma en que he enseñado no funcione contigo.
Profesor, buenos días, resulta que cuando mi objeto colisiona con los bloques estáticos la colisión no comienza en el momento desde que se juntan los objetos si no más bien cuando mi jugador está casi en el centro del bloque, me explico, se supone que al momento de mi jugador juntarse con cualquier extremo del bloque debería darme gameover, pero no me resulta así, si no que la colisión la detecta cuando ya está casi en el centro del objeto bloque, algo que se ve muy irreal al momento de preparar mi video juego.
ResponderBorrarEso es extraño… ¿Estás usando la función "intersects" aquí vista? ¿Sí estás haciendo la evaluación de colisión después de la del movimiento de los objetos?
BorrarTodo lo estoy haciendo al pie de como usted lo está haciendo, la única diferencia es que el tamaño de mi canvas es de 550 x 550 y la velocidad de mi jugador es igual de 10 en todas las posiciones de x e y.
BorrarIncluso, me he dado cuenta que la velocidad la tengo en 10 igual que la del este ejemplo y se visualiza como que estuviera una velocidad de 5 en x e y.
BorrarSuena a un error bastante particular. ¿Tienes forma de compartirme tu código? Puede ser a través de JSFiddle.net
BorrarEste es el código alijado en codepen https://codepen.io/alexis-carmona/pen/ddPmWX
BorrarY creo que cuando colisiona en vertical(dirección en y) se nota más el error.
BorrarNo veo ningún error en tu código, sólo el detalle de qué la posición de las paredes no son múltiplos de 10, por lo cual no están alineadas al movimiento del personaje que se mueve de 10 en 10 pixeles, por ello la colisión se detecta hasta que el personaje esta dentro de la pared.
BorrarSí lo que buscas son movimientos y colisiones mas en tiempo real, te recomiendo que leas el tutorial mas adelante de cómo crear un juego de laberinto
Gracias, me pondré al día con ese capitulo.
BorrarSi, creo que el problema de los bloques era que no eran multiplos de 10, ahora los hice multiplos y se borró un poco el problema, la otra parte la estaré aprendiendo en el tutorial de más adelante.
Borrar¡Excelente! ¡Mucho éxito! Felices códigos
BorrarEste comentario ha sido eliminado por el autor.
ResponderBorrar