Skip to main content
Reinventing the wheel

Desarrollando un motor de videojuego en JavaScript

Continuando con los experimentos con tecnologías web, y después de haber jugado un poco con Canvas 2D, toca el turno a investigar cómo construir un motor de videojuego en HTML5 y JavaScript.

Hace tiempo que había experimentado con la idea, desarrollando un concepto en Flash ActionScript 3 (si tienes el plugin de Flash instalado puedes jugar aquí hasta finales de 2020), y ahora que los navegadores están más que preparados para ser una plataforma de videojuegos (y lo demuestra la gran cantidad de motores HTML5 u juegos que hay disponibles) me decidí a programar el mío propio; no con afán comercial, si no por el placer del aprendizaje.

El resultado está aquí disponible: https://github.com/raohmaru/sge.
Y un ejemplo de algo parecido a un juego: https://raohmaru.github.io/sge/demo/selfish-gene/index.html.

El concepto del motor es sencillo: consta de una vista (por ahora sólo Canvas 2D es soportado) donde se renderiza el juego cada frame, la animación es controlada con window.requestAnimationFrame() para poder llegar a los 60 fps, utiliza el concepto de sprites para manipular los elementos del juego, y tiene un sistema de detección de colisiones simplicísimo.

// get the Canvas lement from the DOM  
var canvas = document.getElementById(‘maincanvas’);

// New instance of the game  
var game = new sge.Game(canvas, {  
    // Game settings  
    renderer: ‘2D’, // Use Canvas2D  
    size: sge.cnst.FILL_WINDOW,  
    width:100%,  
    height:100%,  
    canvasColor: ‘#c2dcfc’,  
    fps: 60  
});

// Create a sprite  
var sp = new sge.sys.Sprite({  
    x : 200,  
    y : 200,  
    width : 32,  
    height: 32  
});  
// Init the sprite’s view  
sp.createView();  
// Fill the sprite with color black  
sp.getView().drawRect(0, 0, sp.width, sp.height, ‘#000);  
// Add the sprite to the game  
game.spriteMgr.add(sp);

// Add on frame update listener  
game.on(sge.event.FRAME, function(){  
    // Moves the sprite in circles  
    sp.x += Math.sin(sp.age/10) \* 10 | 0;  
    sp.y += Math.cos(sp.age/10) \* 10 | 0;  
});

// Start the game  
game.start();  

El renderizador

En un navegador web podemos utilizar diferentes métodos para presentar gráficos: el mismo DOM, imágenes SVG, manipulando los píxeles de un Canvas 2D, o con WebGL. Cuando un nuevo motor SGE se inicia, el usuario puede seleccionar que tipo de renderizador utilizar, el cuál actúa como una pieza desacoplada: a través de una interfaz agnóstica, el motor del juego pasa instrucciones al renderizador seleccionado para que se actualice cada fotograma.

Entity-Component-System

Cada elemento que se puede añadir al juego se considera una entidad (entity en inglés), al cuál se le pueden añadir rasgos (o «componentes») que añaden nuevas características o comportamientos a la entidad. Finalmente, un sistema se encarga de la lógica detrás de cada rasgo (como mover una entidad que tiene el rasgo Movable, o comprobar los puntos de vida del rasgo Destroyable).

// Create a new Entity  
var hero = new sge.sys.Entity({  
    // Properties for the trait Renderable  
    x : 200,  
    y : 200,  
    width : 32,  
    height: 32,  
    // Properties for the trait Movable  
    speed: 4,  
    // Properties for the trait Destroyable  
    hp: 8  
});  
// Add some traits  
hero.addTrait(‘Renderable’, sge.sys.trait.Renderable);  
hero.addTrait(‘Movable’, sge.sys.trait.Movable);  
hero.addTrait(‘Destroyable’, sge.sys.trait.Destroyable);  

Este patrón de arquitectura se conoce como Entity-Component-System, y es el más utilizado en el desarrollo de videojuegos. Una de las ventajas que ofrece es que permite crear nuevos objetos por composición (nuestra entidad Hero tiene los rasgos Renderable, Movable y Health), en contraste con la creación de objetos por herencia (en este caso nuestro entidad Hero tendría una larga cadena de herencias: Hero << LivingEntity << MovableEntity << Entity).

Puedes encontrar más información sobre este patrón en

Colisiones

Todo motor de videojuegos que se precie debe gestionar las colisiones entre los elementos, es decir, cuando un sprite se superpone a otro sprite. Con el trait Solid se añade esta propiedad, permitiendo al sprite chocar con cualquier otro sprite solido en la escena

¿Pero qué ocurre si hay miles de sprites en pantalla? Por cada sprite se debe comprobar si éste colisiona con cualquiera del resto, en un bucle que recorre todos los sprites disponibles tantas veces como sprites haya en pantalla, sesenta veces por segundos. Es un método poco eficiente, y el motor intenta dar una solución más optima a este problema.

[SGE Atlas de sprites, reduciendo así el número operaciones a realizar.

Conclusiones

Si bien construir un motor de videojuego moderno, eficiente y competitivo es una tarea descomunal, es un ejercicio interesante para entender la arquitectura y los retos que presenta este tipo de software. Sin duda rendimiento y facilidad de uso son sus factores más importantes. JavaScript y el elemento Canvas permiten crear motores que cumplan con estas expectativas, convirtiendo a la Web en la plataforma perfecta para disfrutar de videojuegos en el navegador.