En una videoaventura, al final te encuentras que tienes un montón de IFs comprobando cosas y luego un montón de acciones:
. Y así un montón.
Esto significa que cada juego tiene un montón de código específico que, además, ocupa un huevo.
Ahí entra el scripting. Mediante un script, codificas todas esas comprobaciones y acciones de motor en un bytecode. El bytecode y el intérprete que lo ejecuta ocupan mucho menos que una ristra de IFs. Además, esto te permite seguir teniendo tu motor "limpio" de cosas específicas de un juego.
- Al empezar el juego. Esto nos permite configurar un poco algunas cosas o inicializar los flags.
- Cuando entramos en una pantalla, para modificar el escenario, por ejemplo.
- Cuando pulsamos la tecla de interactuar, para examinar o usar un objeto.
- Cuando matamos a un enemigo, por si hay que contar cuantos van o hacer cualquier otra cosa.
...
En la churrera (nuestro engine) hemos optado por incluir scripting para definir el gameplay. Así, el motor se queda limpio. El juego de El Hobbit, por ejemplo, tiene el mismo motor de siempre, pero un script diferente que define donde están los objetos, donde se usan, qué pasa cuando se coge algo, comprueba que lo hayamos hecho todo... en definitiva, el script define el gameplay.
Hemos diseñado nuestro lenguaje de scripting de forma clausal. El programa se divide en secciones, y cada sección contiene una serie de clausulas. Una cláusula no es más que un montón de comprobaciones y una lista de comandos asociados. Si se cumplen todas las comprobaciones, se ejecutan los comandos. Hemos usado este modelo porque es suficiente para lo que necesitamos y porque es tremendamente sencillo de interpretar.
1.- Compila el código en un bytecode fácil de interpretar. Es parecido a una especie de lenguaje de ensamblador, para entendernos.
2.- Genera el código C con el intérprete del bytecode. De este modo, sólo se genera código que interprete las órdenes y comprobaciones que hay en el bytecode, ahorrando espacio.
El motor principal llama al intérprete diciéndole qué sección del código contiene las comprobaciones pertinentes, y el intérprete hace su labor.
Este es el código compilado. Arriba hay una ristra de bytes con el script compilado, y luego está el intérprete que se genera para interpretar ese script.
Código: Seleccionar todo
// msc.h
// Generado por Mojon Script Compiler de la Churrera
// Copyleft 2011 The Mojon Twins
// Script data & pointers
extern unsigned char mscce_0 [];
extern unsigned char mscce_1 [];
extern unsigned char mscce_2 [];
extern unsigned char mscce_3 [];
extern unsigned char mscce_4 [];
extern unsigned char mscce_5 [];
extern unsigned char mscce_6 [];
extern unsigned char mscce_7 [];
extern unsigned char mscce_8 [];
extern unsigned char mscce_9 [];
extern unsigned char mscce_10 [];
extern unsigned char mscce_11 [];
extern unsigned char mscce_12 [];
extern unsigned char msccf_0 [];
extern unsigned char msccf_1 [];
extern unsigned char msccf_2 [];
extern unsigned char msccf_3 [];
extern unsigned char msccf_4 [];
unsigned char *e_scripts [] = {
mscce_3, mscce_6, 0, mscce_8, mscce_7, mscce_12, mscce_11, 0, 0, 0, mscce_9, 0, mscce_10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, mscce_5, 0, 0, 0, mscce_2, 0, 0, mscce_4, 0, 0, 0, mscce_0, mscce_1
};
unsigned char *f_scripts [] = {
msccf_1, msccf_2, 0, 0, msccf_3, msccf_4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, msccf_0
};
#asm
._mscce_0
defb 0x15, 0xF0, 0xFF, 0x01, 0x01, 0x00, 0x01, 0x02, 0x00, 0x01, 0x03, 0x00, 0x01, 0x04, 0x00, 0x01, 0x05, 0x00, 0x01, 0x06, 0x00, 0xFF, 0xFF
._mscce_1
defb 0x29, 0x10, 0x03, 0x0D, 0x10, 0x05, 0x00, 0xFF, 0x01, 0x05, 0x01, 0xE3, 0x00, 0x00, 0x00, 0x2C, 0x21, 0x00, 0x23, 0x35, 0x25, 0x36, 0x21, 0x00, 0x25, 0x33, 0x34, 0x21, 0x00, 0x21, 0x22, 0x29, 0x25, 0x32, 0x34, 0x21, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0x23, 0x10, 0x01, 0x00, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x35, 0x33, 0x23, 0x21, 0x00, 0x21, 0x2C, 0x00, 0x27, 0x21, 0x2E, 0x24, 0x21, 0x2C, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0xFF
._mscce_2
defb 0x21, 0xF0, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x35, 0x2E, 0x00, 0x22, 0x35, 0x25, 0x2E, 0x00, 0x24, 0x29, 0x21, 0x00, 0x25, 0x2E, 0x00, 0x28, 0x2F, 0x22, 0x22, 0x29, 0x34, 0x2F, 0x2E, 0x00, 0x00, 0xEE, 0xFF, 0xFF
._mscce_3
defb 0x07, 0xF0, 0xFF, 0x20, 0x02, 0x02, 0x13, 0xFF, 0xFF
._mscce_4
defb 0x09, 0x10, 0x05, 0x00, 0xFF, 0x20, 0x09, 0x00, 0x0F, 0xFF, 0xFF
._mscce_5
defb 0x21, 0xF0, 0xFF, 0xE3, 0x00, 0x22, 0x35, 0x33, 0x23, 0x21, 0x00, 0x25, 0x2C, 0x00, 0x34, 0x25, 0x33, 0x2F, 0x32, 0x2F, 0x00, 0x24, 0x25, 0x2C, 0x00, 0x24, 0x32, 0x21, 0x27, 0x2F, 0x2E, 0x00, 0xEE, 0xFF, 0xFF
._mscce_6
defb 0x0B, 0xF0, 0xFF, 0x20, 0x0A, 0x01, 0x18, 0x20, 0x0B, 0x01, 0x19, 0xFF, 0xFF
._mscce_7
defb 0x11, 0x10, 0x06, 0x00, 0xFF, 0x20, 0x05, 0x04, 0x12, 0xE3, 0x00, 0x00, 0x2F, 0x34, 0x29, 0x21, 0xEE, 0xFF, 0xFF
._mscce_8
defb 0x23, 0x10, 0x06, 0x01, 0xFF, 0xE3, 0x26, 0x29, 0x32, 0x25, 0x00, 0x30, 0x21, 0x32, 0x21, 0x00, 0x30, 0x2F, 0x2E, 0x25, 0x32, 0x33, 0x25, 0x00, 0x25, 0x2C, 0x00, 0x21, 0x2E, 0x29, 0x2C, 0x2C, 0x2F, 0x00, 0xEE, 0xFF, 0xFF
._mscce_9
defb 0x23, 0x10, 0x06, 0x01, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x30, 0x25, 0x32, 0x2F, 0x00, 0x25, 0x2C, 0x00, 0x21, 0x2E, 0x29, 0x2C, 0x2C, 0x2F, 0x00, 0x2D, 0x21, 0x32, 0x25, 0x21, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0xFF
._mscce_10
defb 0x21, 0xF0, 0xFF, 0xE3, 0x30, 0x2F, 0x32, 0x00, 0x21, 0x31, 0x35, 0x29, 0x00, 0x21, 0x2E, 0x24, 0x21, 0x00, 0x27, 0x2F, 0x2C, 0x2C, 0x35, 0x2D, 0x00, 0x23, 0x21, 0x22, 0x32, 0x25, 0x21, 0x2F, 0xEE, 0xFF, 0xFF
._mscce_11
defb 0x20, 0xF0, 0xFF, 0xE3, 0x23, 0x35, 0x29, 0x24, 0x21, 0x2F, 0x00, 0x31, 0x35, 0x25, 0x00, 0x33, 0x29, 0x00, 0x34, 0x25, 0x00, 0x36, 0x25, 0x00, 0x34, 0x25, 0x00, 0x2D, 0x21, 0x34, 0x21, 0xEE, 0xFF, 0xFF
._mscce_12
defb 0x46, 0xF0, 0xFF, 0x20, 0x06, 0x04, 0x14, 0x20, 0x07, 0x04, 0x15, 0x20, 0x06, 0x03, 0x16, 0x20, 0x07, 0x03, 0x17, 0x20, 0x05, 0x01, 0x1A, 0x20, 0x06, 0x01, 0x1B, 0x20, 0x07, 0x01, 0x1C, 0x20, 0x08, 0x01, 0x1D, 0x51, 0x00, 0x40, 0xFF, 0x4F, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x2C, 0x00, 0x34, 0x25, 0x32, 0x32, 0x29, 0x22, 0x2C, 0x25, 0x00, 0x33, 0x2D, 0x21, 0x35, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0xFF
._msccf_0
defb 0x2A, 0x10, 0x06, 0x01, 0x51, 0x05, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x25, 0x00, 0x30, 0x2F, 0x2E, 0x25, 0x33, 0x00, 0x25, 0x2C, 0x00, 0x21, 0x2E, 0x29, 0x2C, 0x2C, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xE1, 0x32, 0x33, 0xE0, 0x06, 0xFF, 0x28, 0x10, 0x06, 0x01, 0x50, 0x05, 0xFF, 0xE3, 0x00, 0x00, 0x21, 0x31, 0x35, 0x29, 0x00, 0x25, 0x2C, 0x00, 0x21, 0x2E, 0x29, 0x2C, 0x2C, 0x2F, 0x00, 0x2E, 0x2F, 0x00, 0x33, 0x29, 0x32, 0x36, 0x25, 0x00, 0x00, 0x00, 0xEE, 0xE1, 0xE0, 0x04, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0xFF
._msccf_1
defb 0x2A, 0x13, 0x01, 0x01, 0x20, 0x02, 0x02, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x25, 0x35, 0x2E, 0x25, 0x00, 0x21, 0x00, 0x2C, 0x2F, 0x33, 0x00, 0x25, 0x2E, 0x21, 0x2E, 0x2F, 0x33, 0xEE, 0xE1, 0xE0, 0x07, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0xFF, 0xFF
._msccf_2
defb 0x29, 0x22, 0x10, 0x21, 0x21, 0x91, 0xC0, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x36, 0x25, 0x2E, 0x24, 0x2F, 0x00, 0x2D, 0x2F, 0x34, 0x2F, 0x00, 0x33, 0x25, 0x2D, 0x29, 0x2E, 0x35, 0x25, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xE1, 0xE0, 0x07, 0xFF, 0xFF
._msccf_3
defb 0x31, 0x13, 0x06, 0x01, 0x20, 0x05, 0x04, 0xFF, 0x20, 0x05, 0x04, 0x00, 0xE0, 0x08, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x25, 0x33, 0x00, 0x25, 0x2C, 0x00, 0x21, 0x2E, 0x29, 0x2C, 0x2C, 0x2F, 0x00, 0x35, 0x2E, 0x29, 0x23, 0x2F, 0x00, 0xEE, 0xE1, 0xE0, 0x07, 0x01, 0x06, 0x01, 0x50, 0x02, 0x16, 0x12, 0xFF, 0xFF
._msccf_4
defb 0x30, 0x22, 0x40, 0x4F, 0x21, 0x80, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x2D, 0x21, 0x35, 0x27, 0x00, 0x34, 0x25, 0x00, 0x36, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xE1, 0xE0, 0x07, 0xE0, 0x04, 0xE0, 0x08, 0xE0, 0x04, 0xF0, 0xFF, 0x0D, 0x22, 0x40, 0x4F, 0x21, 0x00, 0x7F, 0xFF, 0x51, 0x00, 0x10, 0xFF, 0x1F, 0xFF, 0x2D, 0x22, 0x10, 0x1F, 0xFF, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x2C, 0x00, 0x34, 0x25, 0x33, 0x2F, 0x32, 0x2F, 0x00, 0x25, 0x33, 0x00, 0x34, 0x35, 0x39, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xE1, 0xE0, 0x07, 0xE0, 0x06, 0xE0, 0x08, 0xE0, 0x06, 0xF1, 0xFF, 0xFF
#endasm
unsigned char *script;
void msc_init_all () {
unsigned char i;
for (i = 0; i < MSC_MAXITEMS; i ++)
items [i].status = 0;
for (i = 0; i < MSC_MAXFLAGS; i ++)
flags [i] = 0;
}
unsigned char read_byte () {
unsigned char c;
c = script [0];
script ++;
return c;
}
unsigned char read_vbyte () {
unsigned char c;
c = read_byte ();
if (c & 128) return flags [c & 127];
return c;
}
// Ejecutamos el script apuntado por *script:
unsigned char run_script () {
unsigned char res = 0;
unsigned char terminado = 0;
unsigned char continuar = 0;
unsigned char x, y, n, m, c;
unsigned char *next_script;
if (script == 0)
return;
script_something_done = 0;
while (1) {
c = read_byte ();
if (c == 0xFF) break;
next_script = script + c;
terminado = continuar = 0;
while (!terminado) {
c = read_byte ();
switch (c) {
case 0x10:
// IF FLAG x = n
// Opcode: 10 x n
x = read_vbyte ();
n = read_vbyte ();
if (flags [x] != n)
terminado = 1;
break;
case 0x13:
// IF FLAG x <> n
// Opcode: 13 x n
x = read_vbyte ();
n = read_vbyte ();
if (flags [x] == n)
terminado = 1;
break;
case 0x20:
// IF PLAYER_TOUCHES x, y
// Opcode: 20 x y
x = read_vbyte ();
y = read_vbyte ();
if (!((player.x >> 6) >= (x << 4) - 15 && (player.x >> 6) <= (x << 4) + 15 && (player.y >> 6) >= (y << 4) - 15 && (player.y >> 6) <= (y << 4) + 15))
terminado = 1;
break;
case 0x21:
// IF PLAYER_IN_X x1, x2
// Opcode: 21 x1 x2
x = read_byte ();
y = read_byte ();
if (!((player.x >> 6) >= x && (player.x >> 6) <= y))
terminado = 1;
break;
case 0x22:
// IF PLAYER_IN_Y y1, y2
// Opcode: 22 y1 y2
x = read_byte ();
y = read_byte ();
if (!((player.y >> 6) >= x && (player.y >> 6) <= y))
terminado = 1;
break;
case 0x50:
// IF NPANT n
// Opcode: 50 n
n = read_vbyte ();
if (n_pant != n)
terminado = 1;
break;
case 0x51:
// IF NPANT_NOT n
// Opcode: 51 n
n = read_vbyte ();
if (n_pant == n)
terminado = 1;
break;
case 0xF0:
// IF TRUE
// Opcode: F0
break;
case 0xFF:
// THEN
// Opcode: FF
terminado = 1;
continuar = 1;
script_something_done = 1;
break;
}
}
if (continuar) {
terminado = 0;
while (!terminado) {
c = read_byte ();
switch (c) {
case 0x01:
// SET FLAG x = n
// Opcode: 01 x n
x = read_vbyte ();
n = read_vbyte ();
flags [x] = n;
break;
case 0x20:
// SET TILE (x, y) = n
// Opcode: 20 x y n
x = read_vbyte ();
y = read_vbyte ();
n = read_vbyte ();
map_buff [x + (y << 4) - y] = n;
map_attr [x + (y << 4) - y] = comportamiento_tiles [n];
draw_coloured_tile (VIEWPORT_X + x + x, VIEWPORT_Y + y + y, n);
break;
case 0x32:
// FLICKER
// Opcode: 32
player.estado |= EST_PARP;
player.ct_estado = 32;
break;
case 0x33:
// DIZZY
// Opcode: 33
player.estado |= EST_DIZZY;
player.ct_estado = 32;
break;
case 0x50:
// PRINT_TILE_AT (X, Y) = N
// Opcode: 50 x y n
x = read_vbyte ();
y = read_vbyte ();
n = read_vbyte ();
draw_coloured_tile (x, y, n);
break;
case 0x51:
// SET_FIRE_ZONE x1, y1, x2, y2
// Opcode: 51 x1 y1 x2 y2
fzx1 = read_byte ();
fzy1 = read_byte ();
fzx2 = read_byte ();
fzy2 = read_byte ();
f_zone_ac = 1;
break;
case 0xE0:
// SOUND n
// Opcode: E0 n
n = read_vbyte ();
peta_el_beeper (n);
break;
case 0xE1:
// SHOW
// Opcode: E1
sp_UpdateNow ();
break;
case 0xE3:
x = 0;
while (1) {
n = read_byte ();
if (n == 0xEE) break;
sp_PrintAtInv (LINE_OF_TEXT, LINE_OF_TEXT_X + x, 71, n);
x ++;
}
break;
case 0xF0:
script_result = 2;
terminado = 1;
break;
case 0xF1:
script_result = 1;
terminado = 1;
break;
case 0xFF:
terminado = 1;
break;
}
}
}
script = next_script;
}
return res;
}