viernes, 4 de julio de 2025

Conectividad WebRTC para Videojuegos Multijugador en JavaScript

馃幃 RtcParty.js: Conectividad WebRTC para Videojuegos Multijugador en JavaScript

RtcParty.js es un m贸dulo ligero en JavaScript que facilita el manejo de conexiones WebRTC basadas en datos, pensado especialmente para videojuegos multijugador, pero 煤til para cualquier aplicaci贸n que requiera comunicaci贸n P2P eficiente.

Este m贸dulo encapsula toda la l贸gica de se帽alizaci贸n, conexi贸n, sincronizaci贸n de estados y comunicaci贸n en red, permitiendo que te concentres en lo m谩s importante: el juego.


馃П Niveles de uso

Este documento est谩 organizado en orden de mayor a menor nivel de abstracci贸n. Si solo quieres hacer funcionar una partida multijugador con lobby y lista de jugadores, empieza desde aqu铆. Si necesitas m谩s control o tienes casos especiales, puedes ir bajando a los niveles inferiores.


馃敐 1. RTC.RTCMultiplayer(...) – Soluci贸n completa con interfaz de usuario

Esta funci贸n implementa todo lo necesario para una experiencia multijugador:

  • Pantalla de bienvenida

  • Ingreso de nombre

  • Selecci贸n de tipo de conexi贸n: p煤blica, privada, local o QR

  • Lobby con lista de jugadores, 铆conos, selecciones y estado de "listo"

  • Disparo autom谩tico del inicio cuando todos est谩n listos

Ideal para:

Juegos casuales, prototipos r谩pidos, experiencias multijugador completas.

Ejemplo:

var rtcMp = RTCMultiplayer( "3.236.221.36", // ip ice server "server.com", // dominio del servidor websocket self_id => console.log("onOpen", self_id), // se ejecuta al obtener tu ID guest_id => console.log("onGuest", guest_id), // un jugador se ha conectado guest_id => console.log("onUnguest", guest_id), // un jugador se ha desconectado (guest_id, data, time, target) => console.log("onMessage", guest_id, data, time, target), // mensaje recibido popularChoice => console.log("onReady", popularChoice), // todos listos, empieza el juego guest_id => console.log("onChangeReady", guest_id), // alguien marc贸 como "listo" () => console.log("show config game section"), // se debe mostrar la secci贸n de configuraci贸n del juego ); // Cuando termines la secci贸n de configuraci贸n, llama: rtcMp.show(); // muestra el lobby // Dentro de la configuraci贸n puedes asignar 铆cono y selecci贸n: rtcMp.setIcon("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEA...."); rtcMp.setChoice("opcion 1"); // En el juego puedes enviar mensajes con: rtcMp.commit("Hola a todos");

馃帹 Estilo visual para

RTC.RTCMultiplayer

La interfaz visual generada por RTC.RTCMultiplayer puede personalizarse f谩cilmente. El sistema ya incluye clases de CSS para las distintas pantallas y elementos de la interfaz.

Aqu铆 tienes un conjunto de estilos base que puedes usar como punto de partida para integrar con el dise帽o de tu juego:


/* Administra la pantalla de bienvenida */ #RTCMultiplayerltiplayer.splash {} /* Pantalla para ingresar el nombre */ #RTCMultiplayerltiplayer.requestName {} /* Pantalla para elegir tipo de conexi贸n */ #RTCMultiplayerltiplayer.menu {} /* Pantalla para escribir el nombre de la sala privada */ #RTCMultiplayerltiplayer.requsetRoom {} /* Pantalla de lobby (estilo libre, personaliza aqu铆) */ #RTCMultiplayerltiplayer.lobby { /* puedes agregar estilos como display:grid, borders, etc. */ } /* Estilo base para campos de entrada */ #RTCMultiplayerltiplayer input {} /* Estilo base para botones */ #RTCMultiplayerltiplayer button {} #RTCMultiplayerltiplayer button.Scan {} #RTCMultiplayerltiplayer button.changeName {} /* Indicador visual para el bot贸n enfocado (煤til para teclados o TV remotos) */ #RTCMultiplayerltiplayer button:focus::after {}

馃搶 Recomendaciones

  • Todos los estilos est谩n encapsulados dentro del ID #RTCMultiplayerltiplayer, lo que facilita su integraci贸n sin afectar el resto de tu UI.

  • Puedes sobrescribir cualquier clase agregando tus propios estilos al final de tu archivo CSS.

  • Si usas rutas personalizadas para las im谩genes (scan.png, user.png, selector.png, PachangaLogo.png), aseg煤rate de ajustar las URLs seg煤n tu estructura de proyecto.


馃柤️ ScreenShots



馃搧 Archivos

Los archivos necesarios son RtcParty.js, coloredQR.js, jsQR.js, qrcode.js que puedes encontrarlos ac谩

Para el servidor Turn Stun y Room Dealer Websocket aqu铆. Recuerda que necesitaras un dominio con ssl, o correrlo solo en local.
https://drive.google.com/drive/folders/1bqa7436mbJoTJgtpsoaVaz4c204EyJuz?usp=sharing


馃寪 2. RTC.roomDealer(...) – Conexi贸n autom谩tica v铆a WebSocket

Permite conectar autom谩ticamente a los jugadores en una sala, usando un servidor WebSocket para se帽alizaci贸n. Internamente utiliza RtcParty.

Ideal si ya tienes una interfaz personalizada pero quieres evitar manejar ofertas y respuestas manualmente.


馃 3. RTC.localPlayer(...) – Conexi贸n por c贸digos QR (sin servidor)

Permite que los jugadores se conecten entre s铆 localmente usando WebRTC puro. Utiliza c贸digos QR para compartir la informaci贸n de conexi贸n (ofertas/contraofertas).

Requiere las librer铆as:

  • jsQR.min.js

  • qrcode.js

  • coloredQR.js


馃И 4. RTC.singlePlayer(...) – Modo sin red

Modo de operaci贸n sin conexi贸n para pruebas o juegos de un solo jugador. Ejecuta localmente todos los eventos como si estuviera en red.


馃暩️ 5. RTC.RtcParty(...) – Red P2P en malla

Conecta m煤ltiples jugadores en una red de malla usando WebRTC. Cada jugador comparte sus conexiones con los dem谩s.

Permite:

  • Invitar jugadores con invite(id)

  • Aceptar conexiones con acceptInvite(id, offer)

  • Enviar mensajes con commit(data, [targets])

  • Escuchar eventos como onGuest, onData, etc.

Ideal si quieres manejar la l贸gica de conexi贸n directamente, pero con soporte multicliente.


馃敡 6. RTC.RTC(...) – Conexi贸n punto a punto b谩sica

Nivel m谩s bajo: conexi贸n entre dos puntos usando WebRTC. Es necesario encargarse manualmente de la negociaci贸n de ofertas y respuestas.

Ejemplo de uso:

const peer = RTC.RTC("3.236.221.36", onOpen, onState, onError, onMessage); peer.createOffer(offer => { // Enviar 'offer' al otro usuario }); // Luego, con la respuesta del otro usuario: peer.acceptContraOffer(answer); // Enviar datos: peer.send("hola");

馃棧️ ¿Qu茅 opci贸n deber铆a usar?

NecesidadFunci贸n recomendada
Quiero todo listo con interfaz visualRTC.RTCMultiplayer
Tengo mi propia interfaz pero quiero que se conecten f谩cilRTC.roomDealer
Quiero jugar en local sin servidorRTC.localPlayer
Quiero simular red sin red realRTC.singlePlayer
Quiero controlar conexiones entre varios usuariosRTC.RtcParty
Solo necesito conectar dos usuarios directamenteRTC.RTC

mi茅rcoles, 3 de febrero de 2021

Simple RayCast 2D - javascript

Permite mostrar gr谩ficos estilo Wolfenstein de los 80s, con el peque帽o plus (o des, seg煤n se vea) de darle relevancia al piso y esconder el techo.

Puedes descarga el script desde la siguiente link Download

Instalaci贸n

Debe incluirse la clase dentro el cuerpo del html. Para usarse al insarse al instante debe ir dentro de la etiqueta <head> para cargarse antes del body. De lo contrario, puede cargarse en el body, y debe usarse hasta confirmar que ha cargado.

<script type="simple_raycast2D.js"></script>

Inicializar el motor

Para comenzar debemos hacer una instancia de simple_raycast2D

// inicia el motor gr谩fico RayCast2D
rc = new simple_raycast2D(
    canvas, // elemento html canvas
    maxx, // profundidad del rayo casteador
);

El primer argument es un elemento htm canvas, que es posible crearlo en el instante o tomar uno que ya se encuentre en el body. El segundo argumento es la profundidad de campo o 谩rea visible.

Por ejemplo:

rc = new simple_raycast2D(
    canvas = document.createElement('canvas')
    maxx = 16
);

Tama帽o del canvas

El motor no usa la tarjeta gr谩fica, en cambio usa el procesador. Por esta raz贸n el tama帽o de dibujo del canvas debe ser limitado a usar baja resoluci贸n para que corra bien.

A parte, sabemos que se le puede dar un tama帽o de impresi贸n independiente al tama帽o de dibujo del canvas. La funci贸n calcula autom谩ticamente el ancho de dibujo del canvas.

Esto se especifica con el siguiente estilo y la siguiente funci贸n

canvas {
    width: 100vw;
    height: 100vh;
    position: fixed;
    left: 0;
    right: 0;
}

rc.resizeTo(scr_height = 90);

donde src_height especifica la altura del canvas. Recomiendo que sea entre 80 y 160 pixeles. 

Cada que la ventana cambie de tama帽o o la orientaci贸n, deber铆a llamarse.

window.addEventListener('resize',function(){
	rc.resizeTo(90);
});
rc.resizeTo(90);

El mapa

Para especificar un mapa debemos definir dos variables, tiles y mapa. Donde tiles es un arreglo de bits correspondiente a la imagen que se usara como tile map y puede extraerse usando la funci贸n imData. Y mapa en un arreglo bidimencional de 16bits.

rc.tiles = imData('mapTexture.png',true);
rc.mapa = [
    [0,0,0,0],
    [0,0,0,0],
    [0,0,0,0]
];

Por ejemplo, el array para el mapa es de 4x3. Y donde se esperan n煤meros de 16 bits.

  • El bit 1 indica si es pared.
  • El bit 2 indica si debe comportarse como diagonal,
  • Los bit 3,4,5,6,7,8 indican el id del sprite 0-63 para la pared,
  • Los bit 9,10,11,12,13,14 indican el id del sprite 0-63 para el piso, 
  • Los bit 15,16 indican como dibujar el piso:
    • 0 - normal,
    • 1 - rotaci贸n 90°,
    • 2 - rotaci贸n 180°,
    • 3 - rotaci贸n -90°


En seguida un ejemplo de c贸mo usar mapas separados

mapa_es_pared = [
    [1,0,0,1],
    [1,0,0,1],
    [1,1,1,1]
];
mapa_sprites_pared = [
    [5,0,0,5],
    [4,0,0,4],
    [5,6,5,5]
];
mapa_sprites_piso = [
    [0,4,6,0],
    [0,7,5,0],
    [0,0,0,0]
];
mapa_piso_rotacion = [
    [0,0,1,0],
    [0,3,2,0],
    [0,0,0,0]
];
mapa = [];
for(var y=0; y<3; y++){
    mapa[y] = [];
    for(var x=0; x<4; x++)
        mapa[y][x] = mapa_es_pared[y][x] +
            (mapa_sprites_pared[y][x] << 2) +
            (mapa_sprites_piso[y][x] << 8) +
            (mapa_piso_rotacion[y][x] << 14);
}
rc.mapa = mapa;

La camara b谩sica

Para posicionar la c谩mara se puede usar el siguiente comando

rc.cam({x: 1, y: 1, a:0});

donde "xy" es la posici贸n de la c谩mara dentro del mapa, y "a" es el 谩ngulo en radianes que la c谩mara estar谩 mirando.

Los objetos

Para agregar objetos a la escena, primero debes crear las texturas de las cuales se tomaran los gr谩ficos. Eso se hace con el siguiente comando.

var sprite_texture = rc.textura(imData('sprite.png'), width, height);

La textura se carga asincronamente, de modo que no necesitas esperar a que cargue para poderla usar.

La textura debe contener los sprites del objeto organizados horizontalmente por cara y verticalmente por animaci贸n. Es decir, cada columna de sprites dentro de la imagen corresponde a un 谩ngulo o cara del objeto, y cada rengl贸n corresponde a un cuadro de animaci贸n.

Ahora, ya podemos crear un objeto con el siguiente comando

obj_n = rc.crear(
    sprite_texture, // indica la textura que usar谩 el objeto
    angulos, // indica la cantidad de caras del objeto     angulos, // indica la cantidad de caras del objeto     divY, // indica la cantidad de cuadros del objeto     ancho, // indica el ancho con el que ser谩 dibujado el objeto     animacion, // es un array que indica el orden de los frames     inverso,     sorteable );

Para el array que indica el orden de frames de animaci贸n, en el caso deuna imagen de 4x3 sprites ser铆a [0,4,8], pensando en que usa 4 caras, y una animaci贸n de 3 cuadros.

Posicionar y animaci贸n de objetos

Para indicar la posici贸n de un objeto usa el siguiente comando

rc.pos(obj_n, {
    x: 2,    // posici贸n x
    y: 1.5,    // posici贸n y
    z: 0,    // posici贸n altura
    a: 0,    // angulo
    v: true    // visible
});

El primer argumento es el 铆ndice devuelto por la funci贸n crear. El segundo atributo es un objeto con cualidades de la entidad, donde ning煤n atributo del objeto es obligatorio.

Tambi茅n puedes indicar una animaci贸n con el siguiente comando. Donde el primer argumento es el 铆ndice devuelto por la funci贸n crear. El segundo argumento es un objeto con la informaci贸n de la animaci贸n.

rc.anim(obj_n, {
    c: 0, // indica el cuadro donde comienza la animaci贸n
    l: 3, // indica la duraci贸n de la animaci贸n
    r: 2, // indica cu谩ntos cuadros rebobinar谩 al terminar la animaci贸n
    s: 0.1, // indica la velocidad de la aniamci贸n
});

por ejemplo,  en la imagen tenemos en el primer cuadro de pie, y en los siguientes dos la animaci贸n de caminar. Para ponerle a caminar, comenzamos en el cuadro 0, donde est谩 de pie, y especificamos una duraci贸n de 3 cuadros, que son uno de pie y dos de caminado, y por 煤ltimo 2 cuadros de retroceso al terminar, para que se repitan los cuadros de caminado.

Loop Update

Para terminar, debemos llamar la funci贸n dibujar a cada instante para actualizar y redibujar

rc.dibujar(showWalls, showFloor);

donde el primer argumento es un valor booleano para indicar si debe dibujar las paredes, y el segundo indica si debe dibujar el piso.

DOCUMENTACION

/*

// inicia el motor gr谩fico RayCast2D
rc = new simple_raycast2D(
    canvas, // elemento html canvas
    maxx, // profundidad del rayo casteador
);

// para cargar especificar la textura del piso y las paredes
rc.tiles = imData('mapTexture.png',true);

// adem谩s se puede cargar una imagen escala de grises de protuberancia para el piso
rc.tilesDepth = imData('depthTexture.png',true);

// para cargar el mapa, se hace con un array bidimencional de W x H,
// donde se esperan numeros de 16bits. El bit(1) indica si es pared, el bit(2) indica si
// debe comportarse como diagonal, el bit(3,4,5,6,7,8) es el id del sprite 0-63 para la pared,
// el bit (9,10,11,12,13,14) indica el id del sprite 0-63 para el piso,  el bit(15,16) indican
// como dibujar el piso: 0 nada, 1 rotaci贸n 90°, 2 rotaci贸n 180°, 3 rotaci贸n -90°
rc.mapa = [[0]];

// posiciona la camara opc = {x, y, a}
rc.cam(opc);

// posiciona la camara aditivamente opc = {x, y, a}
rc.camMov(opc);

// posiciona la camara autom谩ticamente con forme al mapa sobre el objecto n. A partir de
// la posici贸n actual de la camara voltea a ver al objeto n y lo rodea "ang" grados,
// acercandose o alejandose a "z" disancia
rc.camAuto(n, ang, z);

// posiciona la camara en alineaci贸n camara, n, m, a una distancia z de n
rc.camAtack(n,m,z);

// carga una textura. "width, height" es el acho y
// alto en pixeles de la imagen. Es necesario proporcionarlos para la carga ascincrona
// si se precarga, basta con "width" 
rc.textura(imagenData, width, height);

// se encarga de actualizar el canvas. "showWalls" es un booleano que especifica si debe mostrar
// la paredm "showFloor" es un booleano que especifica si debe mostrar el piso, "es3D" es un booleano
// que especifica si dibujar谩 lateralmente otra camar谩 para efecto 3D, "_x,_y" especifica la posici贸n en
// el canvas donde comenzar谩 a dibujar, __paletteChange es un objecto que pretende cambia run color por
// otro, por ejemplo {0:20} (el 12 por el 20), pero tambien puede ser una sucici贸n de colres {0:[20, 21, 15]}
rc.dibujar(showWalls, showFloor, false, false, false, es3D, _x, _y, __paletteChange);

// Crea un objecto con la textura "textura" create previamente, misma que est谩 dividida en "angulos"
// o columnas que representan las caras del objeto, y dividida en "divY" renglones de animaci贸n, presentando en pantalla con un "ancho" factor
// correspondiente a el tama帽o de los bloques, "animaci贸n" si es definido debe ser un array con el nuevo
// orden de los renglones de animaci贸n, "inverso" es un voleano que indica si las caras est谩n en orden
// inverso, "sorteable" es un boleano que especifica si se dibujar谩 en orden de distancia
rc.crear(textura, angulos, angulos, divY,ancho, animacion, inverso, sorteable);

// elimina el objeto "n"
rc.borrar(n){ this.obj_activo[n] = false; ;

// posiciona el objecto "n" { x:pos,y:pos,z:pos, a:angulo, v:boolean_visible }
rc.pos(n, opc);

// posiciona aditivamente el objecto "n" { x:pos,y:pos,z:pos, a:angulo, v:boolean_visible }
rc.mov(n, opc);

// anima un objeto "n", comienza en el renglon "opc.c" y avanza "opc.l" renglones, entonces retrocede
// "opc.r" renglones para realizar un loop
rc.anim(n, opc);

// crea un objecto emporal tomado el objeto "n" y lo anima desde el renglon "opc.c" hasta "opc.l"
// renglones, en la posici贸n "opc.x, opc.y" cuando termina se destruye amenos que "isTemp" sea
// definido igual a false
rc.animX(n, opc, isTemp);

// devuelve los objectos que se encuentran en el arco de posici贸n "x,y", de angulo "a", de arco "arc"
// a una distancia m谩xima "max"
rc.getObjectsByRayCast(x,y,a,arc_size,max);

// devuelve los objetos que tocan una barra que inicia en "x,y", de angulo "a", un grosor "w" y
// un largo "max"
rc.getObjectsByRayCastAndBar(x,y,a,w,max);

// devuelve la imagen de ruta "url", donde "isBlocks" es booleano  que especifica si servir谩 para
// paredes y techo (true - que pondra la secuencia por cuadro), o para objetos (false - que no
// pondra secuencia).
imageData = imData(url,isBlocks);

// actualiza el tama帽o de la pantalla al tama帽o html del canvas, donde scr_height es el alto en piexeles
// por default 90
rc.resizeTo(scr_height);
*/