Crear un framework para juegos en Javascript y Canvas de HTML5: lightCyan
¿Qual es el objetivo de este post?
Crear un framework simple y ligero en Javascript para la creación de juegos en Canvas de HTML5 que sirva como base para futuros post. Una vez creado el Framework, realizaré una pequeña demo donde controlaremos un rectángulo a través de las flechas del teclado.
- Resultado final del tutorial AQUI (no es demasiado espectacular, pero creo que es un buen punto de inicio al mundo del desarrollo de juegos con HTML5).
- Código fuente: https://github.com/pmphp/lightCyan
¿Porqué otro post sobre cómo empezar a programar juegos en HTML5?
El framework que desarrollaremos tiene una estructura “inspirada” en cyanJS. Es un framework pensado para facilitar futuros tutoriales y muy orientado al aprendizaje.
¡Que comience la fiesta!
Éste primer post va a ser bastante denso ya que quiero explicar lo mejor posible cómo he hecho el framework. Si os interesa el desarrollo de juegos en Canvas de HTML5, os recomiendo que intentéis entender todos los pasos (aunque tengáis pensado utilizar frameworks ya creados, siempre va bien entender la base de las cosas).
Lo primero que vamos a crear, cómo es lógico, es el archivo index.html:
<!doctype html> <html lang="es"> <head> <meta charset="UTF-8"> <title>Project-Cyan.com</title> <!-- Cargamos el archivo Javascript del juego --> <script src="js/lightCyan.js"></script> </head> <body> <canvas id="canvas" width="500" height="400" style="border:1px solid #000;"> <p>Explorador no compatible</p> </canvas> </body> </html> |
¡Dicho y hecho! (fácil eh!? Tranquilos que ahora vienen las curvas
!)
El segundo paso es crear un archivo llamado “lightCyan.js” que va ser el que contendrá nuestro framework.
Vamos a crear una función anónima que encapsule todas las funciones / variables de nuestro framework y que al final exporte los métodos que deseamos convertir en públicos a una variable global:
// Archivo lightCyan.js (function (window) { // Aquí escribiremos la parte privada del framework. window.lightCyan = { // Parte pública del framework. }; }(window)); |
Por si alguien no está familiarizado con este tipo de estructura, lo que estamos creando es una función que se llama a sí misma y crea una variable llamada “lightCyan” en el contexto global (es decir, podremos acceder a ella desde cualquier lugar). Con esto nos aseguramos de que no va a haber colisión de variables con otros JS.
Vamos a añadir las variables necesarias para nuestro framework:
(function (window) { var aGameObjects = []; // P1 var aToRemove = []; // P2 var nFps = 24; // P3 var nDrawInterval = 0; // P4 var oCanvas = { main : null, mainContext : null, buffer : null, bufferContext : null }; // P5 window.lightCyan = {}; }(window)); |
- (P1) aGameObjects: En esta variable del tipo Array, añadiremos todos los elementos del juego.
- (P2) aToRemove: En esta variable añadiremos los elementos que queramos eliminar del juego.
- (P3) nFps: Número de frames por segundo al que se ejecutará el juego.
- (P4) nDrawInterval: Intervalo de tiempo en el que se ejecutará el juego. Por ejemplo, si queremos 24FPS, el juego se ejecutará cada 0.0416 segundos.
- (P5) oCanvas: Objeto con la información del elemento Canvas y del buffer.
- main: Referencia al elemento Canvas de nuestro index.html
- mainContext: Contexto del elemento Canvas principal.
- buffer: Elemento Canvas que crearemos más adelante y que va a ser oculto.
- bufferContext: Contexto del elemento Canvas buffer.
Si no os queda claro para qué sirve alguna variable, no os preocupéis. Más adelante veremos la implementación de cada una.
Para cada una de las variables vamos a crear métodos públicos que nos permitan modificarlas.
Método “setFps” (P3 nFps):
window.lightCyan = { setFps : function (nNewFps) { if (typeof nNewFps === 'number') { nFps = nNewFps; } } }; |
Éste método es muy simple: Recibe un parámetro, comprueba que sea numérico y modifica el valor de la variable privada nFps.
Método “setCanvas” (P5 oCanvas):
window.lightCyan = { setFps : /*....*/, setCanvas : function (sCanvasId) { var oMainCanvas; if (typeof sCanvasId === 'string') { oCanvas.main = document.getElementById(sCanvasId); if (oCanvas.main !== null) { oCanvas.mainContext = oCanvas.main.getContext('2d'); oCanvas.buffer = document.createElement('canvas'); oCanvas.buffer.width = oCanvas.main.width; oCanvas.buffer.height = oCanvas.main.height; oCanvas.bufferContext = oCanvas.buffer.getContext('2d'); } } } }; |
Éste método espera recibir como parámetro un string con el atributo ID del canvas principal. Si el parámetro es un string, intenta acceder al elemento canvas con dicha ID. Si no ha habido ningún problema, creamos el contexto del Canvas, y creamos un nuevo elemento canvas que utilizaremos como buffer. También creamos el contexto del canvas que utilizaremos como buffer.
Método “addGameObject” (P1 aGameObjects):
Éste método es algo más complejo que los anteriores. La idea de este método es guardar objetos del juego en el array, para más adelante tener acceso a dichos elementos. Utilizaremos el patrón “modulo” para crear los objetos del juego. Antes de analizar el método, veamos cómo sería la introducción de un objeto del juego:
lightCyan.addGameObject("Identificador del objeto", function () { return {}; }); |
Por lo tanto, addGameObject esperará recibir dos parámetros: El identificador del objeto, y la función del objeto. (Explicación en forma de comentarios dentro del código)
window.lightCyan = { setFps : /*....*/, setCanvas : /*...*/, addGameObject : function (sObjectId, fpObjectBuilder) { // Variable donde guardaremos el objeto del juego. var oFinalObject = null; if (typeof sObjectId === 'string') { // Ejecutamos la función y guardamos el objeto resultante. oFinalObject = fpObjectBuilder(); if (typeof oFinalObject === 'object') { // Añadimos una propiedad ID para identificar al objeto. oFinalObject.__id = sObjectId; // Añadimos el objeto a la colección de objetos. aGameObjects.push(oFinalObject); } } } }; |
Con esto, ya podríamos añadir objetos a nuestro juego (aunque de momento no hagamos nada con ellos).
Método “removeGameObject” (P2 aToRemove):
Éste método sirve para, dada una ID, eliminar el objeto con dicha ID de la colección.
window.lightCyan = { setFps : /*....*/, setCanvas : /*...*/, addGameObject : /*...*/, removeGameObject : function (sObjectId) { if (typeof sObjectId === 'string') { var oCurrentGameObject = null; var nObjectCount = 0; var nGameObjectsLength = aGameObjects.length; for (nObjectCount = 0; nObjectCount < nGameObjectsLength; nObjectCount += 1) { oCurrentGameObject = aGameObjects[nObjectCount]; if (oCurrentGameObject.__id === sObjectId) { aToRemove.push(nObjectCount); } } } } }; |
Tiene la particularidad de que NO elimina directamente el objeto del array, sino que guarda en la variable “aToRemove” la posición del objeto dentro del array “aGameObjects“. Esto es así porqué más adelante recorreremos el array de objetos (aGameObjects) en varias ocasiones. Si justo eliminásemos un objeto del array aGameObjects cuando estamos recorriéndolo, provocaríamos un fatal error.
Método “startGame”:
Éste método nos servirá para inicializar la ejecución del juego.
window.lightCyan = { setFps : /*....*/, setCanvas : /*...*/, addGameObject : /*...*/, removeGameObject : /*...*/, startGame : function () { nDrawInterval = 1 / nFps * 1000; setInterval(fpGameInterval, nDrawInterval); } }; |
Lo único que debemos tener claro, es que setInterval llamará la función fpGameInterval (que veremos más adelante) cada nDrawInterval milisegundos (cada 0.0416 segundos en caso de 24FPS).
Recopilando lo echo hasta el momento:
Vamos a ver cómo está nuestro archivo lightCyan.js por el momento:
(function (window) { var aGameObjects = []; var aToRemove = []; var nFps = 24; var nDrawInterval = 0; var oCanvas = { main : null, mainContext : null, buffer : null, bufferContext : null }; // Métodos privados. // Métodos públicos. window.lightCyan = { setFps : function (nNewFps) { if (typeof nNewFps === 'number') { nFps = nNewFps; } }, setCanvas : function (sCanvasId) { var oMainCanvas; if (typeof sCanvasId === 'string') { oCanvas.main = document.getElementById(sCanvasId); if (oCanvas.main !== null) { oCanvas.mainContext = oCanvas.main.getContext('2d'); oCanvas.buffer = document.createElement('canvas'); oCanvas.buffer.width = oCanvas.main.width; oCanvas.buffer.height = oCanvas.main.height; oCanvas.bufferContext = oCanvas.buffer.getContext('2d'); } } }, addGameObject : function (sObjectId, fpObjectBuilder) { var oFinalObject = null; if (typeof sObjectId === 'string') { oFinalObject = fpObjectBuilder(); if (typeof oFinalObject === 'object') { oFinalObject.__id = sObjectId; aGameObjects.push(oFinalObject); } } }, removeGameObject : function (sObjectId) { if (typeof sObjectId === 'string') { var oCurrentGameObject = null; var nObjectCount = 0; var nGameObjectsLength = aGameObjects.length; for (nObjectCount = 0; nObjectCount < nGameObjectsLength; nObjectCount += 1) { oCurrentGameObject = aGameObjects[nObjectCount]; if (oCurrentGameObject.__id === sObjectId) { aToRemove.push(nObjectCount); } } } }, startGame : function () { nDrawInterval = 1 / nFps * 1000; setInterval(fpGameInterval, nDrawInterval); } }; }(window)); |
Hasta el momento hemos creado todos los métodos públicos. Es decir, método accesibles desde fuera de la aplicación. Vayamos a por los métodos privados:
Método privado “fpGameInterval”:
Cómo hemos visto, el método público startGame crea un intervalo que llama a la función fpGameInterval. En cada ejecución del juego (o lo que és lo mismo: a cada frame), vamos a necesitar hacer tres cosas básicas:
- Eliminar objetos que hayamos añadido al array “aToDelete” con el método público “removeGameObject“.
- Lanzar el método update de todos los objetos del juego.
- Lanzar el método draw de todos los objetos del juego.
Por lo tanto, la función fpGameInteval lo único que va a hacer es llamar a las funciones encargadas de ello:
(function (window) { /* Variables privadas */ /*...*/ /* Métodos privados */ var fpGameInterval = function () { oGameExecution.remove(); oGameExecution.update(); oGameExecution.draw(); }; window.lightCyan = { /* Métodos públicos */ }; }(window)); |
Las tres funciones que llama, se encuentran dentro del objeto “oGameExecution”, así que lo siguiente és crear dicho objeto:
(function (window) { /* Variables privadas */ /*...*/ /* Métodos privados */ var fpGameInterval = function () { /*...*/ }; var oGameExecution = { remove : function () {}, update : function () {}, draw : function () {} }; window.lightCyan = { /* Métodos públicos */ }; }(window)); |
Y ahora, toca añadir la funcionalidad a cada función:
Método privado “remove” de oGameExecution:
/* ... */ remove : function () { var nRemoveLength = aToRemove.length; var nCount = 0; var nCurrentObject = 0; for (nCount = 0; nCount < nRemoveLength; nCount += 1) { nCurrentObject = aToRemove[nCount]; aGameObjects.splice(nCurrentObject, 1); } aToRemove = []; }, /* ... */ |
Éste método recorre el array “aToRemove”, y elimina los objetos del array “aGameObjects” a través de la función “splice“. Una vez eliminados todos los elementos, reseteamos el array “aToRemove”.
Método privado “update” de oGameExecution:
/* ... */ update : function () { fpCallGameObjectMethods("update", oCanvas); //Reordenamos los objetos en el eje Z. aGameObjects.sort(function(oObjA, oObjB) { return oObjA.z - oObjB.z; }); }, /* ... */ |
El método update llama la función “fpCallGameObjectMethods“, pasando como parámetros la key “update” y el elemento canvas. Veremos el funcionamiento de fpCallGameObjectMethods más tarde. De momento deberéis creer que ese método llama al método “update” de todos los objetos introducidos en la colección “aGameObjects“.
Método privado “draw” de oGameExecution:
/* ... */ draw : function () { // Limpiamos el área de dibujo de nuestro buffer. oCanvas.bufferContext.clearRect(0, 0, oCanvas.buffer.width, oCanvas.buffer.height); // Llamámos el método "draw" de todos los objetos del juego. fpCallGameObjectMethods("draw", oCanvas); // Limpiamos el área de dibujo de nuestro canvas principal. oCanvas.mainContext.clearRect(0, 0, oCanvas.main.width, oCanvas.main.height); // Finalmente, dibujamos el buffer en nuestro canvas principal. oCanvas.mainContext.drawImage(oCanvas.buffer, 0, 0); } /* ... */ |
Él método “draw” tampoco tiene demasiado misterio:
- Limpiamos el área de dibujo de nuestro contexto buffer con la función “clearRect”. “clearRect” és una función que limpia el área de dibujo que le indiquemos. En nuestro caso, limpiamos del punto “x = 0″ y “y = 0″ hasta “x = oCanvas.buffer.width” y “oCanvas.buffer.height”. Es decir, TODO el área del canvas.
- Llamámos la función “fpCallGameObjectMethods”, índicando la key “draw”. Igual que antes, de momento deberéis creerme cuándo os digo que dicha función llama el método “draw” de todos los objetos de “aGameObjects”.
- Limpiamos el área de dibujo de nuestro contexto principal de la misma forma que en el punto “primero”.
- Pasamos la imagen que hay en el buffer a nuestro canvas principal.
La función fpCallGameObjectMethods:
Después de tanto hablar de ella, es momento de ver qué hace ésta función:
(function (window) { /* Variables privadas */ /*...*/ /* Métodos privados */ var fpGameInterval = function () { /*...*/ }; var fpCallGameObjectMethods = function (sMethodName, oArgs) { var oCurrentGameObject = null; var nObjectCount = 0; var nGameObjectsLength = aGameObjects.length; for (nObjectCount = 0; nObjectCount < nGameObjectsLength; nObjectCount += 1) { oCurrentGameObject = aGameObjects[nObjectCount]; if (oCurrentGameObject[sMethodName]) { oCurrentGameObject[sMethodName](oArgs); } } }; var oGameExecution = { /* ... */ }; window.lightCyan = { /* Métodos públicos */ }; }(window)); |
La función recibe dos parámetros:
- sMethodName: Nombre del método que queremos ejecutar.
- oArgs: Parámetros/Argumentos que queremos pasar al método que se va a ejecutar.
Recorre el array “aGameObjects” y por cada objeto, comprueba si existe el método que hemos pasado dentro de “sMethodName” (si volvemos a mirar el método update y el método draw, veremos que en el primero sMethodName equivale a “update” y en el segundo a “draw”). En caso de existir el método, lo ejecuta.
Volvamos a pararnos para ver qué tenemos hasta el momento:
(function (window) { var aGameObjects = []; var aToRemove = []; var nFps = 24; var nDrawInterval = 0; var oCanvas = { main : null, mainContext : null, buffer : null, bufferContext : null }; var fpGameInterval = function () { oGameExecution.remove(); oGameExecution.update(); oGameExecution.draw(); }; var fpCallGameObjectMethods = function (sMethodName, oArgs) { var oCurrentGameObject = null; var nObjectCount = 0; var nGameObjectsLength = aGameObjects.length; for (nObjectCount = 0; nObjectCount < nGameObjectsLength; nObjectCount += 1) { oCurrentGameObject = aGameObjects[nObjectCount]; if (oCurrentGameObject[sMethodName]) { oCurrentGameObject[sMethodName](oArgs); } } }; var oGameExecution = { remove : function () { var nRemoveLength = aToRemove.length; var nCount = 0; var nCurrentObject = 0; for (nCount = 0; nCount < nRemoveLength; nCount += 1) { nCurrentObject = aToRemove[nCount]; aGameObjects.splice(nCurrentObject, 1); } aToRemove = []; }, update : function () { fpCallGameObjectMethods("update", oCanvas); /** * Reordenamos los objetos en el eje Z. */ aGameObjects.sort(function(oObjA, oObjB) { return oObjA.z - oObjB.z; }); }, draw : function () { oCanvas.bufferContext.clearRect(0, 0, oCanvas.buffer.width, oCanvas.buffer.height); fpCallGameObjectMethods("draw", oCanvas); oCanvas.mainContext.clearRect(0, 0, oCanvas.main.width, oCanvas.main.height); oCanvas.mainContext.drawImage(oCanvas.buffer, 0, 0); } }; // Métodos públicos. window.lightCyan = { setFps : function (nNewFps) { if (typeof nNewFps === 'number') { nFps = nNewFps; } }, setCanvas : function (sCanvasId) { var oMainCanvas; if (typeof sCanvasId === 'string') { oCanvas.main = document.getElementById(sCanvasId); if (oCanvas.main !== null) { oCanvas.mainContext = oCanvas.main.getContext('2d'); oCanvas.buffer = document.createElement('canvas'); oCanvas.buffer.width = oCanvas.main.width; oCanvas.buffer.height = oCanvas.main.height; oCanvas.bufferContext = oCanvas.buffer.getContext('2d'); } } }, addGameObject : function (sObjectId, fpObjectBuilder) { var oFinalObject = null; if (typeof sObjectId === 'string') { oFinalObject = fpObjectBuilder(); if (typeof oFinalObject === 'object') { oFinalObject.__id = sObjectId; aGameObjects.push(oFinalObject); } } }, removeGameObject : function (sObjectId) { if (typeof sObjectId === 'string') { var oCurrentGameObject = null; var nObjectCount = 0; var nGameObjectsLength = aGameObjects.length; for (nObjectCount = 0; nObjectCount < nGameObjectsLength; nObjectCount += 1) { oCurrentGameObject = aGameObjects[nObjectCount]; if (oCurrentGameObject.__id === sObjectId) { aToRemove.push(nObjectCount); } } } }, startGame : function () { nDrawInterval = 1 / nFps; setInterval(fpGameInterval, nDrawInterval); } }; }(window)); |
¡Ya casi hemos terminado con nuestro framework! Tan sólo nos falta poder controlar el evento “onKeyDown” y “onKeyUp“. Pero antes veamos cómo hacer funcionar lo que tenemos hasta el momento:
Volvamos a nuestro archivo index.html y creemos una etiqueta <script></script> dentro del <head> (por debajo de <script src=”js/lightCyan.js”></script>) y añadamos lo siguiente:
window.onload = function () { // Llamamos el método público setCanvas que vimos anteriormente. lightCyan.setCanvas("canvas"); // Establecemos los FPS a 24. lightCyan.setFps(24); // Creamos un objeto llamado "rectangulo" y lo añadimos a la colección aGameObjects // a través del método público addGameObject que vimos anteriormente. lightCyan.addGameObject("rectangulo", function () { var nAxisX = 10; var nAxisY = 10; var nWidth = 25; var nHeight = 25; return { // Creamos el método draw del objeto "rectangulo". draw : function (canvas) { canvas.bufferContext.beginPath(); canvas.bufferContext.rect(nAxisX, nAxisY, nWidth, nHeight); canvas.bufferContext.fillStyle = "#000"; canvas.bufferContext.closePath(); canvas.bufferContext.fill(); } }; }); // ¡Iniciamos la ejecución del juego! lightCyan.startGame(); } |
Si abrimos el archivo index.html desde un explorador, podréis ver que en nuestro canvas hay un rectángulo dibujado. Aunque no os lo parezca, dicho rectángulo se está pintando 24 veces cada segundo… ¿maravilloso verdad?
Bien, hecho este pequeño “kit kat” volvamos a nuestro lightCyan.js. Lo siguiente y último de éste tutorial va a ser que nuestro framework detecte el evento “keyUp” y “keyDown” y que cómo consecuencia, que nuestro rectángulo se mueva.
Primero, bindeamos el evento “keyDown” y el evento “keyUp”:
(function (window) { /* Variables privadas */ /* Métodos privados */ window.addEventListener("keydown", function (eEvent) { oGameExecution.keyPush(eEvent); }, false); window.addEventListener("keyup", function (eEvent) { oGameExecution.keyPush(eEvent); }, false); var fpGameInterval = /*...*/; var fpCallGameObjectMethods = /*... */ ; var oGameExecution = /* ... */; window.lightCyan = { /* ... */ }; }(window)); |
Cómo veis, bindeamos ambos eventos con la función de JS “addEventListener” y le indicamos que, cada vez que se produzca uno de los eventos, llame la función “oGameExecution.keyPush”. Así que cómo ya debéis imaginar, és el momento de crear el método keyPush:
var oGameExecution = { remove : function () { /* ... */ }, update : function () { /* ... */ }, draw : function () { /* ... */ }, keyPush : function (eEvent) { var sEventType = eEvent.type; var nKeyCode = eEvent.keyCode; if (nKeyCode !== 17 && nKeyCode !== 116) { eEvent.preventDefault(); } fpCallGameObjectMethods(sEventType, nKeyCode); } }; |
El método recibe el evento cómo parámetro, a través de dicho evento, obtiene tanto el tipo de evento y el código de la tecla que hemos apretado. Sí el código de la tecla es diferente a “17″ o “116″ (botón F5 y control), llamamos a la función preventDefault para evitar que el explorador ejecute las acciones por defecto para la tecla que hayamos pulsado.
Y finalmente, volvemos a utilizar la función mágica “fpCallGameObjectMethods“. Si sEventType es “keyDown“, ejecutará el método “keyDown” de todos los objetos de la colección “aGameObjects”. Si és “keyUp“, ejecutará el método “keyUp“. En ambos casos les pasará cómo parámetro el código de la tecla.
¡Yaaaaaaaaa casi estamos! Volvamos a nuestro index.html, y modifiquemos nuestro objeto “rectangulo” para que quede de la siguiente forma:
lightCyan.addGameObject("rectangulo", function () { var nAxisX = 10; var nAxisY = 10; var nWidth = 25; var nHeight = 25; var nSpeed = 5; var bMoveRight = false; var bMoveLeft = false; var bMoveUp = false; var bMoveDown = false; return { update : function (canvas) { if (bMoveRight === true) { nAxisX += nSpeed; } if (bMoveLeft === true) { nAxisX -= nSpeed; } if (bMoveUp === true) { nAxisY -= nSpeed; } if (bMoveDown === true) { nAxisY += nSpeed; } }, draw : function (canvas) { canvas.bufferContext.beginPath(); canvas.bufferContext.rect(nAxisX, nAxisY, nWidth, nHeight); canvas.bufferContext.fillStyle = "#000"; canvas.bufferContext.closePath(); canvas.bufferContext.fill(); }, keydown : function (nKeyCode) { if (nKeyCode === 39) { bMoveRight = true; } if (nKeyCode === 37) { bMoveLeft = true; } if (nKeyCode === 38) { bMoveUp = true; } if (nKeyCode === 40) { bMoveDown = true; } }, keyup : function (nKeyCode) { if (nKeyCode === 39) { bMoveRight = false; } if (nKeyCode === 37) { bMoveLeft = false; } if (nKeyCode === 38) { bMoveUp = false; } if (nKeyCode === 40) { bMoveDown = false; } } }; }); |
Hemos añadido el método keydown, keyup y update. Teniendo en cuenta que:
- código de tecla “39″ -> Flecha derecha
- código de tecla “37″ -> Flecha izquierda
- código de tecla “38″ -> Flecha arriba
- código de tecla “40″ -> Flecha abajo
Cambiamos el valor de “bMoveDown/Up/Right/Left” dependiendo de si pulsamos o dejamos de pulsar una tecla y en el update sumamos o restamos el valor de nSpeed en el Eje X o Eje Y.
Si ejecutamos nuestro index.html, ¡¡ya podremos manejar el rectángulo con las flechas del teclado!!
Ahora podemos añadir tantos elementos cómo queramos a nuestro juego a través del método “lightCyan.addGameObject(“id”, función)“, con la ventaja de que cada objeto puede tener su propia función “draw“, “update“, “keyup” y “keydown” completamente independiente del resto de objetos.
Y por hoy creo yo que ya es suficiente
!
¡En futuros tutoriales ampliaré el framework para que se puedan crear módulos nuevos, y muchas más cosas
!
Espero que hayáis encontrado útil el tutorial (¡me he tirado horas y horas redactándolo!) y para cualquier duda ¡¡postear un comentario!!
Recordad:
- Resultado final del tutorial: http://www.project-cyan.com/demos/lightCyan/
- Código fuente: https://github.com/pmphp/lightCyan
Increíble! Está guapísimo Pere
Felicidades
Tengo una ideia similar
Primero me he dedicado a desarollar el Motor Grafico (3D) y ahora Intento hacer una demo basado en la Ideia de Juego de Tron (Motos de Luz)
Muchas gracias por todo el trabajo que estas haciendo. Me esta siendo de mucha ayuda toda la información que estas explicando. Sigue así y espero con ganas la siguiente parte, mientras tanto iré haciendo mis avances con todo lo aprendido. Saludos
Buen trabajo macho, bien explicado y util que es quizá lo mas importante, pues me apunto esta web y ansioso por ver como evoluciona el framework estoy.
- Mucho animo con ello, va a ser mas útil de lo que imaginas.
Están geniales tus tutoriales , gracias
Tengo una duda con este segmento :
if (typeof sObjectId === ‘string’) {
// Ejecutamos la función y guardamos el objeto resultante.
oFinalObject = fpObjectBuilder();
if (typeof oFinalObject === ‘object’) {
// Añadimos una propiedad ID para identificar al objeto.
oFinalObject.__id = sObjectId; }
no debería ser así:
if (typeof sObjectId === ‘string’) {
// Ejecutamos la función y guardamos el objeto resultante.
oFinalObject.__id = sObjectId
if (typeof oFinalObject === ‘object’) {
// Añadimos una propiedad ID para identificar al objeto.
oFinalObject = fpObjectBuilder(); }
No. Si lo hacemos tal y como dices, oFinalObject siempre contendrá null, así que al intentar asignar un valor a la propiedad oFinalObject.__id nos saldrá un error.
La idea de
oFinalObject = fpObjectBuilder();
if (typeof oFinalObject === ‘object’) {
es asegurarnos que la función fpObjectBuilder() nos haya retornado un objeto. Si no nos ha retornado un objeto es que algo ha ido mal (posiblemente, que el módulo del objeto esté mal construido).
Saludos!
ok gracias
Excelente el tutorial, no solo para programar juegos, sino me vino espectacular para aprender algunas tecnicas que desconocia absolutamente. Mil gracias por tu generosidad.
Una duda que tengo, cuando usas cosas del tipo un monton de variables privadas, por ejemplo var nAxisX = 10;
var nAxisY = 10;
var nWidth = 25;
var nHeight = 25;
var nSpeed = 5;
var bMoveRight = false;
var bMoveLeft = false;
var bMoveUp = false;
var bMoveDown = false;
Mi duda es la siguiente: ¿Seria mejor en vez de usar tantas variables usar un objeto con todas ellas dentro o un array? ¿Que es más óptimo para el navegador? Siempre he tenido dudas con esto.
Gracias.
Piensa que las variables sólo se declaran cuando se crea la entidad. Durante la ejecución del juego NO se redeclaran a cada frame. Así que durante la ejecución de cada frame no va a haber diferencia alguna entre haber declarado 8 variables o tan solo una que las contenga todas.
La lógica me dice que:
Si declaras como un objeto único, a cada frame tendrás que acceder a:
- 1: La variable que contiene el objeto.
- 2: La propiedad del objeto.
Si declaras varias variables, sólo tienes que acceder a:
- 1: La variable.
Ahora bién, se deberia montar un JsPerf para comprobar que la “lógica” se cumple
!
De todas formas, a veces para mantener algo de “limpieza” en el código y no crear un monstruo, es mejor organizar las variables en objetos.
Hey! Muy bueno tu tutorial! Gracias por el trabajo que estas haciendo, me ayuda a aprender JS. Espero tener novedades de la continuacion pronto! Saludos! e.
No me lo he leido pero lo voy a hacer, ya que lo poco que he visto se ve que esta muy bien hecho, muy agradecido y sigue asi, un saludo.