Motor de videojuego

JavaScript Experiments: Desarrollando un motor de videojuego

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).

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 spritesLa solución pasa por dividiar la pantalla de juego en una rejilla (de un tamaño por celda predefinido) en el que cada celda (o sector) contiene los sprites cuya posición coincide con el área del sector. Al hacer la comprobación de colisiones de un sprite, ésta se realiza contra los sprites que pueden estar a distancia de colisión (en el mismo sector o en los adyacentes), 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.


Imagen de cabecera: FreeImages.com/Helmut Gevert

Publicado por

Raúl Parralejo

Raúl Parralejo

Frontend developer, especializado en el desarrollo de aplicaciones web y juegos para móvil y escritorio.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *