19 años en Internet

26 junio 2025

¿Aprendiendo a programar para MS-DOS con DIV2 Games Studio? Ejemplos sencillos (capítulo 1)

    Esta entrada tiene como objetivo enseñar a utilizar Div2 Games Studio a través de una serie de ejemplos sencillos. El motivo de esta publicación es que se trata de uno de los primeros IDE que aprendí a usar y me gustaría fomentar su uso para los amantes de la programación de videojuegos en MS-DOS. Y bueno, siendo sinceros, existen numerosos aspectos que abordaré en esta serie de entradas que habría deseado que me fueran explicados en su día.

     En este primer capítulo me centraré únicamente en cuatro sencillos ejercicios: un “Hola Mundo”, un “Hola Mundo” en tres dimensiones en modo 7, un "Hola Mundo" en tres dimensiones en modo 8, con texto giratorio y un sistema básico de novela visual. A lo largo del capítulo se explicarán paso a paso las acciones a realizar, pero como habréis notado por el resumen de dichos ejercicios, la dificultad será incremental y el nivel de detalle de las descripciones irán bajando en cada uno. No obstante, la solución a los ejercicios se encuentra disponible en mi perfil de GitHub, por si fuera necesario consultarlo para resolver dudas (https://github.com/LeHamsterRuso/DIV2Examples/).

 

Material necesario:

  • Ordenador con MS-DOS, FreeDOS, Windows 95, Windows 98 u ordenador actual con emulador DOSBox.
  • DIV2 Games Studio.
  • MS-DOS y FreeDOS: 2MB de RAM y tarjeta VESA o compatible (los ejemplos están configurados a 640x480 y 256 colores).
  • Windows 95 y Windows 98: 16 MB de RAM (se recomiendan 24). 

  

Primer ejemplo, el "Hola Mundo".

Objetivos:

  1. Presentar el menú "Programa".
  2. Manejo básico de las ventanas.
  3. Introducción a las funciones básicas set_mode, set_fps, write y key. 
  4. Manejo de la ayuda. 
  5. Mostrar un texto en pantalla.
  6. El texto debe de estar centrado.

Pasos: 

    Abre DIV2 y dentro de la ventana "Menú PROGRAMAS" clica en "Nuevo...". Si no ves dicho menú, puedes abrirlo desde el botón "Programas" del menú principal.

    Una vez cliques en "Nuevo programa..." se te abrirá una popup preguntando cómo quieres llamar tu proyecto. Escribe HELLOWORLD.PRG y clica en "Aceptar". DIV2 no tiene el concepto de "solución", por lo que no hay un fichero global que embarque todas las fuentes que contendrá tu proyecto. De hecho, este fichero "PRG" realmente es un fichero de texto plano (un fichero "txt" de toda la vida) que contendrá el código fuente de tu aplicación.

 

    Si te fijas, el fichero que se abrirá se llama "HELLOWOR.PRG" en vez de "HELLOWORLD.PRG". Esto pasa porque en MSDOS el nombre de los ficheros está limitado a 8 caracteres (sin contar la extensión) y para evitar problemas DIV2 te trunca el nombre.

    La ventana del programa por defecto es muy pequeña. Puedes probar a desplazarla clicando en la barra del titulo de la ventana (el icono cambia a una mano). También puedes alterar su tamaño clicando en el pequeño recuadro que hay en la esquina inferior-derecha: 

    A continuación escribe el siguiente código fuente:

PROGRAM HELLOWORLD;

BEGIN

    set_mode(m640x480);

    set_fps(30, 0);

    write(0, 320, 240, 4, "Hello World!");

    LOOP

        IF (KEY(_ESC))

            exit("Bye", 0);

        END

        FRAME;

    END

END

    Si te fijas en la sintaxis, DIV2 no tiene noción de llaves ni de sangrías, todos los bloques (bucles, condiciones, procesos y funciones, etc) se cierran mediante la primitiva END.

    Además, todo programa tiene que empezar PROGRAM y el nombre de su proceso y éste debe de tener vinculado obligatoriamente un BEGIN (para separar la definición de variables de lo que es la funcionalidad). Esto sería el equivalente al método "Main" en Java o C#.

    La función set_mode sirve para definir la resolución de la pantalla y m640x480 es una constante del sistema que indica que la resolución a aplicar es 640x480. Si deseáramos poner una resolución de 320x240 o de 1024x768, deberías de utilizar las respectivas constantes m320x240 y m1024x768. Si planeas hacer un juego para MS-DOS, mi consejo es que no te salgas de 320x200, 320x240 o 640x480, básicamente porque el manejo de memoria en MS-DOS está muy limitado y a más resolución tendrás que cargar gráficos más pesados. Piensa que de normal MS-DOS es un sistema operativo pensado para máquinas que hacen uso de apenas de un par de megas de memoria RAM y que si haces un juego que exprima todo el potencial de DIV2 seguramente te irá bien en máquinas con Windows 95 o 98, pero debería de darte problemas en ordenadores más humildes (como por ejemplo viejos 386/486 con menos de 4MB de RAM).

    Tengo que matizar que cuando DIV2 salió a la venta, ya era normal tener ordenadores de 16 o 32 MB de RAM, pero también es cierto que en aquella época los ordenadores ya corrían en Windows 95. Por cierto, los juegos que compiléis con DIV2 también correrán en Windows 95 y en Windows 98, pero para hacerlo el sistema operativo os abrirá una instancia de MS-DOS (es decir, tú clicarás en tu fichero EXE y el sistema operativo te abrirá una ventana de MS-DOS a pantalla completa).

    La función set_fps te permite definir los frames por segundo a los que correrá tu juego. En la época no era normal jugar a 60 o 120 fps, por lo que mi consejo es que elijas cifras entre 20 y 30 fps. Puedes poner perfectamente 60 fps en tu juego, luego que pueda correr a esa velocidad en hardware real ya es otra historia. El segundo argumento de la función es el frame skip, es decir, el número de cuadros que el programa puede saltarse para alcanzar la cifra de fps que indicas. Mi consejo es que lo dejes a 0 (prefiero que el juego se ralentice a que se noten "saltos" bruscos).

    La función write nos permite escribir texto directamente en pantalla. El primer argumento, el 0, indica que no hemos cargado en memoria ninguna fuente y que por ende debemos de cargar las fuentes por defecto de las librerías de DIV2. El segundo y tercer argumento indican las coordenadas X e Y donde escribiremos el texto. En DIV2, las coordenada X es el eje horizontal y va incrementalmente de izquierda a derecha; Por otro lado las coordenada Y es el eje vertical y va incrementalmente de arriba a abajo. O lo que es lo mismo, con una resolución de 640x480, la coordenada x=0 e y=0 es la esquina superior-izquierda, mientras que la coordenada x=640 e y=480 es la esquina inferior-derecha. Al indicar 320x240, estamos ordenando escribir en el centro de la pantalla. El cuarto argumento es el código de centrados y el valor 4 indica que el eje de "masa" del texto está en el centro exacto de la cadena (puedes entender un string como una cadena de caracteres); Es decir, debemos de posicionar el centro del texto "Hello World" en el centro exacto de la pantalla y la alineación del texto estará también centrada. Podemos decir que el código de centrado realmente es el tipo de alineación del texto, pero prefiero hablar de centro de masa porque es algo que se utiliza también en otro tipo de componentes.

    Posiciona el cursor encima de la función write y pulsa la tecla "F1". Se te abrirá la ventana de ayuda, donde te describirá todos los argumentos de la función al detalle. Cabe destacar que tanto DIV como DIV2 venían con un libro muy detallado, que a forma de glosario, explicaba todas las funciones del lenguaje. No obstante, por motivos lógicos (han pasado casi 30 años y no sé dónde los he metido y puede que éste sea también tu caso) y prácticos, resulta mil veces más sencillo consultar la ayuda posicionándose en una función y pulsando "F1".


     Volvamos al código. La primitiva LOOP es el equivalente a día de hoy a un "while(true)", es decir, a un bucle infinito. Y dentro de ese bucle infinito hacemos dos cosas: Verificamos si se ha pulsado la tecla de "Escape" para salir del juego y hacemos una llamada a FRAME en cada iteración. Con FRAME ordenamos dibujar en pantalla, es decir, forzamos un cuadro, mientras que con KEY verificamos si la tecla "escape" ha sido pulsada en el cuadro actual. Si no pusiéramos ese "FRAME", el texto que hemos indicado en el write nunca saldría impreso en pantalla y además entraríamos en un bucle infinito del que no nos sería posible salir. Por último tenemos la función "exit", donde el usuario verá un mensaje de despedida al salir del juego y le indicaremos un segundo parámetro como código de salida. Estos códigos suelen emplearse más para la gestión de errores (en caso que quieras forzar, por ejemplo, una excepción)... en MS-DOS, un programa exitoso normalmente retorna 0, mientras que un valor diferente de 0 (generalmente 1) indica un error o un estado no exitoso.

     Pulsa la tecla "F10", el programa compilará y arrancará (y recuerda pulsar "escape" para cerrar el programa).


 

Segundo ejemplo, el "Hola Mundo" en modo 7.

Objetivos:

  1. Manejo de la ayuda.
  2. Introducción al modo 7. 
  3. Introducción a las regiones y al scroll (para efectos de parallax). 
  4. Mostrar un "Hola Mundo!" prerenderizado y girar la cámara alrededor de él.
  5. Desplazamiento de parallax. 

     El modo 7 es un efecto que fue bastante popular en la época de las consolas de 16 bits y que consiste en abatir un plano para dar una falsa sensación de 3D. Ejemplos de este efecto lo tenéis en juegos de SNES como Super Mario Kart, F-Zero o Pilotwings. Debido a las limitaciones de DIV2, en este modo no podemos cargarle personajes poligonales en 3D y la trampa que se hace es la de presentar un personaje 2D visto desde distintos ángulos. Es importante mencionar que ese efecto recibía dicho nombre en la SNES debido a que correspondía a uno de los distintos modos gráficos que ofrecía la consola: el modo 0 utilizaba 4 capas de 4 colores cada una, lo que permitía múltiples niveles de parallax; el modo 1 combinaba 2 capas con 16 colores cada una, más una capa con 4 colores; y el modo 7 permitía una única capa de 256 colores con efectos de rotación y escalado, que en la práctica se utilizaba para crear efectos de perspectiva. Por otro lado, el modo 7 de DIV2 es una imitación limitada de lo que comúnmente hacían los juegos en modo 7 de SNES y se reutilizó el mismo nombre como homenaje.

      El siguiente código que veréis a continuación es una readaptación del "TUTOR7.PRG" de David Navarro (el cual se incluía en los ejemplos de DIV1 y DIV2) y para ejecutarlo necesitaréis recupear el fichero HELLOWOR.FPG de mi github (https://github.com/LeHamsterRuso/DIV2Examples/blob/main/FPG/HELLOWO.FPG):

PROGRAM HELLO_WORLD_MODE7;
GLOBAL
    SpritesText[]=8,             // Number of faces
        3, 10, 9, 8, 7, 6, 5, 4; // ID graphic for each face
BEGIN
    set_mode(m640x480);
    set_fps(30, 0);

    // Default assets file
    load_fpg("HELLOWO.FPG");

    // Camera options
    m7.camera=id;
    m7.height=64;
    m7.distance=96;

    // Sets fake 3D floor
    start_mode7(0, 0, 1, 1, 0, 128);

    // Prints the fake 3D Hello World
    M7Sprite(&SpritesText, 0, 0, 0, -pi/2);

    // Main loop
    LOOP
        if (key(_right))
            angle+=pi/4;
        END
        if (key(_left))
            angle-=pi/4;
        END

        if (key(_ESC)) exit("Bye", 0); END

        FRAME;
    END
END

/* Shows a 2D sprite from different angles
   @xgraph -> Array with the graphic ids for each angle
   @x, y, z -> Coordenates
   @angle -> Angle */
PROCESS M7Sprite(xgraph, x, y, z, angle)
BEGIN
    ctype=c_m7;
    LOOP
        FRAME;
    END
END

 

     Si nos fijamos en el código, tenemos un array SpritesText, que contiene como primer elemento el número de caras prerenderizadas que tendrá nuestro objeto en 3D. Básicamente, si decimos que tendrá 1 cara, el objeto mostrará el mismo gráfico desde todos los ángulos. Si ponemos 2, tendrá una cara desde 0 a pi (180º) y otra desde pi (180º) a 2pi (360º). En nuestro ejemplo tenemos 8 caras, por lo que mostraremos un gráfico si vemos el objeto desde un ángulo de 0 a pi/4, otro desde pi/4 a pi/2... y así sucesivamente hasta 3pi/4 a 2pi. Los números que veis a continuación (3, 10, 9, 8...) son descriptores de fichero que apuntan a un gráfico dentro del archivo HELLOWO.FPG. De hecho, podéis entender los ficheros ".FPG" como una especie de fichero comprimido o ZIP que contiene distintos bitmaps o ficheros gráficos. En pocas palabras, esos 3, 10, 9, 8, etc, representan ficheros de imágenes. Podéis verlo falsamente (para que sea mas inteligible) como si el fichero HELLOWO.FPG fuera una tabla y esos números fueran el ID de un blob.

    Por otro lado, el "start_mode7(0, 0, 1, 1, 0, 128);" es lo que produce la magia. El primer argumento es el número de instancia de modo 7 (podemos tener varias e incluso re-inicializar la actual), el segundo es el ID del fichero por defecto (en este caso como sólo hemos cargado el HELLOWO.FPG, será el que se aplique; Si tuviéramos varios ficheros FPG cargados, tendríamos que memorizar el retorno del LOAD_FPG en una variable y facilitar esta variable como segundo argumento). El tercer argumento es el ID del gráfico que se utiliza como suelo (puedes verlo como la "textura nº 1 del fichero HELLOWO.FPG"). El cuarto es el "suelo" externo: Si fuera 0 veríamos una frontera negra al acabar los límites del suelo y al poner el mismo valor (1) que el gráfico principal, hacemos que éste se vea en forma de mosaico (bucle infinito). El quinto es el identificador de la región de la pantalla, la cual por defecto siempre suele ser 0, pero podemos definir varias regiones para tener, por ejemplo, varias cámaras en nuestro juego. Y por último tenemos ese 128, que indica la altura en el horizonte, es decir,  cuanto alto o bajo está la línea del horizonte en nuestra perspectiva.

    Poco antes tenemos varios valores para la estructura m7 (la cual existe siempre por defecto). Con "m7.camera=id" indicamos que la instancia de gráfico en curso es la que gestiona la cámara: Es decir, si en ese "process" cambiamos las coordenadas x, y, z, angle o xangle, la cámara de nuestra perspectiva se moverá en consecuencia. En lo que concierne a los otros dos valores, son bastante autodescriptivos: Height indica la altura de la cámara respecto al suelo y distance la distancia de la cámara respecto al punto de fuga.

    Respecto al LOOP (el bucle principal), veréis que es terriblemente simple: Las teclas izquierda y derecha hacen girar la cámara, la tecla escape cierra la aplicación y en cada iteración ordenamos el printado de la instancia (con FRAME).

     Por último nos queda la llamada al proceso M7Sprite. Si os fijáis, esto no es un void o un método, si no una instancia de objeto de tipo gráfico. En el mundo de DIV, todo lo que es PROCESS equivale a un constructor personalizado de instancia gráfica: Tienen sus coordenadas X, Y y Z por defecto, tienen un gráfico por defecto (null) y con la definición de M7Sprite simplemente estamos haciendo un overload que nos permite machacar, con extremada simpleza, las variables xgraph, x, y, z, angle de un PROCESS.

     Es decir, si en vez de llamar una vez a M7Sprite lo llamáramos varias veces, tendríamos varias instancias de objetos gráficos en nuestro juego. Por último, dentro de la implementación de M7Sprite, notaréis que indicamos "ctype=c_m7;". Esto nos permite informar que estamos ante un elemento gráfico a mostrar en nuestro mundo en perspectiva... en caso de no asignarle valor, sería un mero elemento del HUD.

    Dale a F10 y ejecuta. Deberías de ver algo así:

     Ahora vamos a editar ligeramente el código para jugar con distintas regiones de pantalla.

 

PROGRAM HELLO_WORLD_MODE7;
GLOBAL
    SpritesText[]=8,             // Number of faces
        3, 10, 9, 8, 7, 6, 5, 4; // ID graphic for each face
BEGIN
    set_mode(m640x480);
    set_fps(30, 0);

    // Default assets file
    load_fpg("HELLOWO.FPG");

    // Camera options
    m7.camera=id;
    m7.height=64;
    m7.distance=96;

    // Define regions
    define_region(1, 0, 0, 640, 128);
    define_region(2, 0, 129, 640, 480);
    start_scroll(0, 0, 11, 2, 1, 15);
    start_mode7(0, 0, 1, 1, 2, 0);


    // Prints the fake 3D Hello World
    M7Sprite(&SpritesText, 0, 0, 0, -pi/2);

    // Main loop
    LOOP
        if (key(_right))
            angle+=pi/4;
            scroll.x0-=8;
            scroll.x1-=16;

        END
        if (key(_left))
            angle-=pi/4;
            scroll.x0+=8;
            scroll.x1+=16;

        END

        if (key(_ESC)) exit("Bye", 0); END

        FRAME;
    END
END

/* Shows a 2D sprite from different angles
   @xgraph -> Array with the graphic ids for each angle
   @x, y, z -> Coordenates
   @angle -> Angle */
PROCESS M7Sprite(xgraph, x, y, z, angle)
BEGIN
    ctype=c_m7;
    LOOP
        FRAME;
    END
END

 

    Si nos fijamos en este código, definimos dos regiones: La primera que va desde las coordenadas x=0 e y=0 hasta x=640 y=128... y la segunda que va desde x=0 e y=129 hasta x=640 e y=640. Cabe destacar que DIV2 nos permite definir un total de 32 regiones (de la 0 a la 31) y que la 0 es la única que no puede ser sobrescrita. ¿Por qué usar regiones? Bueno, son muy útiles. Con ellas puedes hacer fácilmente juegos multijugador con scrolls o instancias de modo 7 distintos. En este ejemplo, por ejemplo, cargamos en la región 1 un scroll de doble parallax y en el segundo tenemos nuestra escena en modo 7. Respecto al start_scroll, sus argumentos son muy similar al del modo 7: El primero es el número de instancia de scroll, el segundo es el descriptor de fichero de los gráficos a emplear, el tercero es gráfico del primer parallax, el cuarto es el gráfico del segundo parallax, el quinto es el número de la región a emplear y el sexto es el código de repetición de los gráficos (con 15 indicamos que los dos parallax deben de estar en mosaico/bucle).

    Haz F10, deberías de ver el siguiente ejempo:


 

 

Tercer ejemplo, el "Hola Mundo" en modo 8.

Objetivos:

  1. Introducción al modo 8.
  2. Uso de constantes.

     El modo 8, conocido antiguamente como “juegos tipo Doom”, produce un efecto poligonal que simula una experiencia tridimensional. Por desgracia tiene sus limitaciones, no nos permite, por ejemplo, hacer rampas (sólo escalones) y el ángulo de visión está limitado en el eje Y (no podemos girar la cabeza 180º y mirara al cielo o al suelo). Tampoco nos permite cargar modelos 3D, por lo que todos los personajes, items, enemigos, ect... que pongamos en nuestro juego tendrán que representarse como en el modo 7 (un array que indique el número de caras y los descriptores de fichero de los gráficos). Aún así es más versátil que el modo 7, puesto que nos permite hacer instancias con distintas texturas (en el modo 7 un mismo gráfico cubre la totalidad del suelo), alturas, posicionar objetos a través de flags (en vez de escribir a pelo las coordenadas), cargar niebla y personalizarla e incluso nos permite definir un skybox (cielo) a nivel del mapa (sin tener que implementarlo en código).

    Para el siguiente ejemplo necesitaréis recupear el fichero HELLOWOR.FPG de mi github (https://github.com/LeHamsterRuso/DIV2Examples/blob/main/FPG/HELLOWO.FPG) y también el HELLO3D.WLD (https://github.com/LeHamsterRuso/DIV2Examples/blob/main/WLD/HELLO3D.WLD):

PROGRAM HELLO_WORLD_3D;
CONST
    SCREEN_SIZE_X = 640;
    SCREEN_SIZE_Y = 480;

    FILE_WLD = "HELLO3D.WLD";
    FILE_FPG = "HELLOWO.FPG";
GLOBAL
    SpritesText[]=8,             // Number of faces
        3, 10, 9, 8, 7, 6, 5, 4; // ID graphic for each face
PRIVATE
    mouse_xBefore;
    zBefore;
    jumpHeight;
    isJumping;
    originalHeight;
    originalEyeHeight;

BEGIN
    set_mode(SCREEN_SIZE_X*1000 + SCREEN_SIZE_Y);
    set_fps(30, 0);

    load_fpg(FILE_FPG);
    load_wld(FILE_WLD, 0);

    start_mode8(id, 0, 0);

    ctype=c_m8;
    height=128;
    radius=64;
    m8.height=128;
    go_to_flag(0);
    mouse_xBefore=mouse.x;
    jumpHeight=0;
    isJumping=0;

    originalHeight = height;
    originalEyeHeight = m8.height;

    set_fog(0, 128);
    set_env_color(100, 100, 100);


    // Prints the fake 3D Hello World
    M7Sprite(&SpritesText, 1, -pi/2);

    loop
        if (key(_esc)) exit("Bye", 0); end

        /* lateral movement */
        if (key(_d)) xadvance(angle-90000,16); end
        if (key(_a)) xadvance(angle+90000,16); end

        /* Walk (SHIFT for run) */
        if (key(_w))
            if(key(_l_shift) or key(_r_shift))
                advance(40);
            else
                advance(10);
            end
        end

        /* Back (SHIFT for run) */
        if (key(_s))
            if (key(_l_shift) or key(_r_shift))
                advance(-20);
            else
                advance(-10);
            end
        end

         /* Jump */
        if (key(_space) and not isJumping and zBefore == z)
            isJumping = 1;
            jumpHeight = 30;
        end

        if (isJumping)
            z = z + jumpHeight;
            jumpHeight = jumpHeight - 2;
            if (jumpHeight <= 0)
                isJumping = 0;
            end
        else
            z -= 25;
        end
        zBefore = z+25;

        /* Duck */
        if (key(_control))
            height = originalHeight * 2 / 3;
            m8.height = originalEyeHeight * 2 / 3;
        else
            height = originalHeight;
            m8.height = originalEyeHeight;
        end

        /* Mouse mouvement (for camera view) */
        angle -= (mouse.x - mouse_xBefore) * 350;
        m8.angle = 128 - (mouse.y*256)/(SCREEN_SIZE_Y);

        if (mouse.x <= 0 or mouse.x >= (SCREEN_SIZE_X-1))
            mouse.x = SCREEN_SIZE_X/2;
        end
        mouse_xBefore = mouse.x;

        frame;
    end
end

/* Shows a 2D sprite from different angles
   @xgraph -> Array with the graphic ids for each angle
   @flag -> Localization
   @angle -> Angle */
PROCESS M7Sprite(xgraph, flag, angle)
BEGIN
    ctype=c_m8;
    go_to_flag(flag);

    LOOP
        FRAME;
    END
END

    Si te fijas en el código previo, hago uso de constantes para definir la resolución y el nombre de los ficheros. Para el caso de la resolución, en el código utilizo varios cálculos para el movimiento que varían en función del alto y ancho máximo de la pantalla (según dónde apunta el ratón), por lo que esto me permite tener parametrizado mi aplicación de forma sencilla: Si el día de mañana deseo compilar el juego en otra resolución, simplemente cambio el valor de las constantes SCREEN_SIZE_X e SCREEN_SIZE_Y y arreando. La diferencia entre una constante y una variable es que la primera se reemplaza en tiempo de compilación y no es modificable en tiempo de ejecución.

    Del principio del código quisiera destacar el "load_wld(FILE_WLD, 0);" que es el encargado de cargar en memoria el mapa del escenario 3D. El cero del segundo argumento hace referencia al fichero FPG que contendrá las texturas y al indicar 0 le decimos que será el FPG cargado por defecto (en este caso, como sólo hemos cargado uno, será ese).  Además, en cuanto definimos las propiedades de la cámara en modo 8, si nos fijamos sale un "go_to_flag(0)". Esto nos permite indicar que las coordenadas X e Y de nuestro proceso serán las que tiene asignadas el flag 0 del fichero WLD cargado. Si el fichero no tuviera inicializada ninguna bandera o flag, esto nos remontaría una excepción en tiempo de ejecución.

     Otra particuliaridad de este modo son las funciones set_fog y set_env_color. El primero nos permite añadir una niebla en nuestro escenario 3D, siendo el primer parámetro su punto de inicio (distancia respecto a la cámara) y el segundo argumento el punto (la distancia) donde la niebla es más espesa; Por su lado el set_env_color nos permite definir el color de la niebla en formato RGB, pero con valores que van del 0 al 100. Al aplicar un valor de R = 100, G = 100 y B = 100, le estamos diciendo que nuestra niebla será blanca.

    También quisiera indicar que en este ejemplo cambio el overload del M7Sprite, haciendo que en vez de pedir las coordenadas, indique un flag donde se posicionará de inicio el objeto.

   Dale a F10, deberías de ver algo similar a esto:


 

Cuarto ejemplo, una novela visual

Objetivos:

  1. Uso de estructuras.
  2. Limpieza de recursos en tiempo de ejecución.
  3. Uso de transparencias vía xput.
  4. Carga de fondos vía put_screen. 

 

PROGRAM VISUAL_NOVEL_EXAMPLE;
GLOBAL
    STRUCT Dialog[4]
        STRING Background;
        STRING Character;
        STRING Text;
    END

    file_fnt;
    file_bg;
    file_dialog_text_bg;
    file_dialog_name_bg;
    dialog_text[5];
PRIVATE
    INT scene = 0;
    INT second = 0;
    INT complete = 0;
BEGIN
    SET_MODE(m640x480);
    SET_FPS(30, 0);
    LoadStaticResources();
    LoadData();
    LoadScene(scene);
    LOOP
        if (second < strlen(Dialog[scene].Text))
            PrintDialog(Dialog[scene].Text, second);
            second++;
        ELSE
            complete = 1;
        END
        IF (key(_esc)) exit("Bye", 0); end
        IF (scan_code != 0)
            IF (complete == 1)
                scene++;
                IF (scene >= 4)
                    scene = 0;
                END
                LoadScene(scene);
                second = 0;
                complete = 0;
            ELSE
                second = strlen(Dialog[scene].Text) - 1;
                PrintDialog(Dialog[scene].Text, second);
            END
        END
        FRAME;
    END;
END

process LoadStaticResources()
BEGIN
   file_fnt=load_fnt("VNEXAMPL.FNT");
   file_dialog_name_bg=load_map("ASSETS/BG/DIALOGCH.MAP");
   file_dialog_text_bg=load_map("ASSETS/BG/DIALOG.MAP");
END

process LoadScene(INT scene)
BEGIN
    delete_text(all_text);
    delete_draw(all_drawing);

    clear_screen();
    unload_map(file_bg);
    file_bg=load_map(Dialog[scene].Background);
    put_screen(0, file_bg);
    xput(0, file_dialog_text_bg, 15, 375, 0, 100, 4, 0);
    xput(0, file_dialog_name_bg, 15, 355, 0, 100, 4, 0);

    write(file_fnt, 127, 365, 4, Dialog[scene].Character);
END

process LoadData()
BEGIN
    MockData();
END

process MockData()
BEGIN
    SetDialog(0, "ASSETS/BG/001.MAP", "Narrator",
        "It was a sweet summer day in this Isekai-style universe.");
    SetDialog(1, "ASSETS/BG/001.MAP", "Narrator",
        "And then she appeared, a sweet green elf...");
    SetDialog(2, "ASSETS/BG/002.MAP", "Gidna",
        "Excuse me, good sir...\nWould you happen to have a few coins for a little beer?");
    SetDialog(3, "ASSETS/BG/003.MAP", "Gidna",
        "Believe it or not,\nI'm allergic to water.");
END

process SetDialog(INT element, STRING Background, STRING Character, STRING Text)
BEGIN
    Dialog[element].Background = Background;
    Dialog[element].Character = Character;
    Dialog[element].Text = Text;
END

 /*
    Prints the dialogs in the scene in multiple lines
    @Text: Full dialog
    @second: Position where cut the lines drawing
*/
process PrintDialog(STRING text, int second)
PRIVATE
    INT i, j;
    INT pos;
    INT prev;
    STRING temp;
    INT line_count;
    INT dialog_y;
    INT copied_last;
    STRUCT Lines[5]
        STRING Text;
    END

BEGIN
    if (second > strlen(text) - 1)
        second = strlen(text) - 1;
    END

    prev = 0;
    line_count = 0;
    copied_last = 0;

    while (line_count < 5)
        // Gets the next line break (if any)
        pos = strstr(text, "\n");

        if (pos == -1 OR pos > second)
            // No more breaks found or it's beyong second
            j = 0;
            for(i = prev; i <= second; i++)
                temp[j] = text[i];
                j++;
            END
            temp[j] = 0;
            strcpy(Lines[line_count].Text, temp);
            line_count++;
            copied_last = 1; // Mark that we already copied the remaining text
            break;
        END

        // Copy from prev to just before the line break
        j = 0;
        for(i = prev; i < pos; i++)
            temp[j] = text[i];
            j++;
        END
        temp[j] = 0;
        strcpy(Lines[line_count].Text, temp);
        line_count++;

        /* Replace the "\n" in the original string
         to avoid matching it again*/
        text[pos] = ' ';
        text[pos + 1] = ' ';

        prev = pos + 2;
    END
    /* If there's still remaining text
     amd we haven't copied it yet */
    if (copied_last == 0 AND prev <= second AND line_count < 5)
        j = 0;
        for(i = prev; i <= second; i++)
            temp[j] = text[i];
            j++;
        END
        temp[j] = 0;
        strcpy(Lines[line_count].Text, temp);
        line_count++;
    END

    // Clear previous dialog lines
    for(i = 0; i < 5; i++)
        if (dialog_text[i] != 0)
            delete_text(dialog_text[i]);
        END
    END

    // Draw the new dialog lines
    dialog_y = 380;
    for(i = 0; i < line_count; i++)
        dialog_text[i] = write(file_fnt, 30, dialog_y, 0, Lines[i].Text);
        dialog_y += 20;
    END
END

    Este último ejemplo parece complicado pero una vez lo relees es bastante sencillo. Si nos fijamos, creamos un array de estructuras "Dialog" en el que almacenamos los diálogos y fondos de cada escena. Al cargar cada escena limpiamos los textos de pantalla y los anteriores fondos. En el bucle principal vamos ordenando que se muestre el texto de forma lenta, caracter a caracter, acción que se realiza mediante el método PrintDialog, el cual también verifica la presencia de saltos de línea "\n" para escribir el texto en varias líneas si fuera necesario. Por otro lado, la función MockData inicializa los parámetros.

     Entre las cosas a señalar está el Struct de Lines que contiene un array de strings. Lo bonito en el mudno de la programación habría sido hacer un array de strings directamente, pero esto no es posible en DIV2 por limitaciones del entorno... No obstante, para bypasear esta limitación, podemos hacer un array de estructuras que contengan un un string. También cabe destacar la función xput, que nos permite añadir gráficos del fichoer FPG con distintos efectos de reescalado, rotación y transparencia (y que en este ejemplo he aprovechado para hacer los cuadros de texto semitransparentes).

    Para que el ejemplo funcione necesitaréis la carpeta MAP de mi github (https://github.com/LeHamsterRuso/DIV2Examples/tree/main/MAP). Una vez la tengas, dale a F10, deberías de ver la siguiente escena en bucle:

  


    Y hasta aquí la entrada de hoy, muchas gracias por llegar hasta el final.
 

16 junio 2025

¿Aprendiendo a programar en Godot 4 .NET? Ejemplos sencillos (capítulo 1)

    Esta entrada tiene como objetivo enseñar a utilizar Godot 4 para .NET a través de una serie de ejemplos sencillos. El motivo de esta publicación es que mi entorno de desarrollo integrado (IDE) para la programación de videojuegos preferido y me gustaría fomentar su uso. Y bueno, siendo sinceros, existen numerosos aspectos que abordaré en esta serie de entradas que habría deseado que me fueran explicados en su día.

     En este primer capítulo me centraré únicamente en tres sencillos ejercicios: un “Hola Mundo”, un “Hola Mundo” en tres dimensiones con texto giratorio (la cámara va rotando alrededor del texto) y un sistema básico de novela visual (donde codificaremos unas sencillas librerías DLL para separar la solución por capas). A lo largo del capítulo se explicarán paso a paso las acciones a realizar, pero como habréis notado por el resumen de dichos ejercicios, la dificultad será incremental. No obstante, la solución a los ejercicios se encuentra disponible en mi perfil de GitHub, por si fuera necesario consultarlo para resolver dudas (https://github.com/LeHamsterRuso/Godot4.NetExamples).

    Por cierto, aunque veáis que las capturas de pantalla de esta entrada corresponden a la versión de Mac de Godot 4, que sepáis que esta guía sirve también tanto para Windows, como Linux.

 

Material necesario:

  

Primer ejemplo, el "Hola Mundo".

Objetivos:

  1. Mostrar un texto en pantalla.
  2. El texto debe de estar centrado.
  3. La ventana debe de ejecutarse maximizada.
  4. El tamaño del texto y su posición debe de adaptarse al tamaño de la pantalla.

Pasos: 

Abre Godot .NET y en la lista de proyectos clica en "+ Crear" (arriba a la izquierda).


 

    En la popup que se abrirá clica en "Examinar" para indicar dónde quieres guardar tu proyecto y ponle un nombre obvio, del estilo "HelloWorld", en rederizador selecciona el modo "compatibilidad", deja el resto de opcioens activadas por defecto y clica en "Crear".


 

     Verás la ventana siguiente:

    A la izquierda, en la pestaña de escena, selecciona "Escena 2D":

 

     Al hacerlo, la vista central cambiará (veremos una especie de canvas 2D en vez de un espacio euclidiano en 3D):


    En la pestaña de Escena, a la izquierda, haz clic derecho sobre el objeto Node2D. Se abrirá una lista desplegable. En esa lista, selecciona la opción "+ Añadir Nodo Hijo...". 


    Se te abrirá una popup de creación de nodos. En la barra de buscar teclea "label" para que se valla filtrando la lista de componentes seleccionables. Selecciona "Label" y dale a "Crear".

 

    Te saldrá algo parecido a esto:


     Con tu componente "Label" seleccionado en la pestaña de "Escena" (a la izquierda), gira tu vista al inspector de propiedades que sale a la derecha. Este inspector te permite, básicamente, alterar las propiedades y atributos del componente que tengas seleccionado en ese instante. Verás que por defecto la primera propiedad propuesta para un label es el "Text" y que éste está vacío. Haz clic en la propiedad y teclea "Hello World" (se trata de un campo de texto editable).

    Acto seguido, en la barra de filtrado de propiedades, busca la palabra "size", despliega el apartado "Theme Overrides" y el subapartado "Font Sizes". En Font Sizes, marca la opción y asigna el valor 32.

 

    Tras cambiar el tamaño de la fuente notarás que el texto pasa a ser legible en el editor (área central de la ventana), pero por desgracia este no está centrado.


     En el editor de escenas, selecciona el botón de modo movimiento. Esto nos permitirá desplazar componentes "a ojo".


     Verás que a nuestro label le han salido dos flechas, una roja y una verde. Clica en él y arrastra el objeto hasta el centro del rectángulo (que por cierto sirve para demarcar el alto y ancho de la pantalla). Da igual si no queda perfectamente centrado, sólo busco enseñarte que el modo movimiento existe.


   Dale al botón de "Play" que hay arriba a la derecha.

 

    La primera vez que ejecutes un proyecto Godot te preguntará cual es su escena principal. Dale "Seleccionar actual" y guarda la escena en curso con un nombre lógico (por ejemplo "HelloWorld.tscn").


     Si lo has hecho bien deberías de ver una ventana que dice "Hello World".

 

    Ahora vamos a hacer que quede más profesional. Cierra la ventana, haz clic derecho en el objeto Node2D, dale a "+ Añadir Nodo Hijo...", busca el componente llamado "CanvasLayer" y añádelo. Haciendo "drag&drop", arrastra el objeto "Label" dentro de tu nuevo "CanvasLayer". La arborescencia de la escena debería de quedar así: Node2D > CanvasLayer > Label.

    Selecciona el label, clica en en el icono de ajustes de anclaje (en el centro) y en la mini popup que se abrirá pulsa en "completo" (un cuadrado blanco).


 

    Verás que ahora ahora tu label ocupa todo el área de la pantalla, pero por desgracia no está centrado. Ve al inspector (a la derecha), borra la barra de filtros (habíamos escrito en ella "size") y selecciona "Center" como valor de las propiedades "Horizontal Alignment" y "Vertical Alignment". Verás que ahora nuestro texto sí que sale perfectamente centrado en la ventana.

 

    Ahora vamos a hacer que nuestro ejemplo ocupe toda la pantalla y no una simple ventana. Accede a "Proyecto> Configuración del Proyecto".

 

    Se te abrirá una nueva popup con toda la configuración de tu proyecto. En la pestaña de "General", selecciona "Visualización > Ventana" y en ella selecciona el modo "Maximized" en la sección de "Tamaño" y "Canvas_items" en el de "Estirar". El "Maximized" nos permite que la ventana arranque maximizada por defecto, mientras que el "Canvas_items" nos permite que todo lo que esté en el canvas conserve sus proporciones cada vez que la ventana sea redimensionada.

 


    Haz clic en "Cerrar" y dale otra vez al botón de "Play". Deberías de ver ahora tu "Hola Mundo" en una ventana que ocupa la totalidad de la ventana. Cabe destacar que podríamos haber seleccionado el modo "FullScreen" en vez del "Maximized" para poder ver el ejemplo a pantalla completa, pero por defecto esto te esconde la barra de la ventana y si no tienes maña con los ordenadores esto suele dar problemas para cerrar la aplicación.
 

 

 

Segundo ejemplo, el "Hola Mundo" en 3D.

Objetivos:

  1. Mostrar un texto 3D en pantalla.
  2. La cámara debe de girar alrededor del texto.
  3. Manejo de assets.
  4. Creación de scripts.
  5. Vínculo de componentes entre el IDE y los scripts.
  6. Introducción al método _Process.

 

    Ahora que hemos entrado en harina iré dando las directrices más rápido. Si tienes maña con Blender 3D puedes crear un objeto 3D de tipo "Text" que diga "Hello World", aplicarle un modificador de "Solidify", ponerle un material de textura negra o gris y exportarlo como objeto fbx o glTF. En la práctica los objetos glTF me dan menos problemas. No obstante, también puedes directamente descargar mi asset 3D desde GitHub (https://github.com/LeHamsterRuso/Godot4.NetExamples/blob/master/002---helloworld-3d/Assets/3D/HelloWorld.glb).


     Una vez tengas tu asset 3D o hayas conseguido el mío, crea un nuevo proyecto y llámalo HelloWorld3D. En la pestaña de "Escena", selecciona "Escena 3D" y en el navegador de recursos (abajo a la izquierda) crea una carpeta "Assets", una subcarpeta "3D" y dentro mete el objeto 3D que has creado o que te has descargado.

 


 

     Haciendo uso de "drag&drop", selecciona el fichero "HelloWorld.glb" y arrástralo dentro de la escena.


     Selecciona el objeto "Node3D" en la pestaña de escena y añádele un objeto WorldEnvironment. Después selecciónalo y en el inspector (a la derecha) créale un nuevo "Environment". Despliega "Background", selecciona en "Mode" el valor "Custom Color" y asigna algún color claro.


     Ahora añade una cámara 3D ("Camera 3D") a tu Node3D y haz uso de las flechas direccionales para centrar el texto (mueve la cámara, no el texto). Si te fijas, en el inspector de la cámara, puedes ver qué es lo que se está viendo a través de ella.



 

     Dale a "Play", deberías de ver tu "Hello World" en 3D.


    Ahora vamos a hacer que la cámara gire al rededor del texto. En tu navegador de recursos crea una carpeta llamada Scripts, haz clic derecho en ella y crea un nuevo script. Te saldrá una nueva popup, en ella selecciona el lenguaje "C#" (muy importante, por defecto te selecciona el "GDScript"), llámalo CameraMovement.cs y haz clic en "Crear".


     Ábrelo con tu editor de código preferido (recomiendo VSCode) y edítalo para que quede así:

 

     Si te fijas en el código, el "Export" espera que el editor le pase un objeto que será por el cual tiene que rotar nuestra cámara. Al mismo tiempo, el método "_Process" se disparará en cada frame, indicandónos en la variable "delta" el lapso de tiempo que ha pasado entre frames.

      Guarda el fichero, vuelve a Godot y dale a la llave inglesa (en la versión de .NET es necesario compilar antes de poder asignar parámetros a nuestro script). Después, selecciona el script que hemos creado y arrástralo a la cámara. Notarás que a nuestra cámara le saldrá un nuevo icono con forma de pergamino (esto nos indica que el objeto tiene ligado un script).



 

     Selecciona la cámara, ve al inspector y en la sección de "CameraMovement" selecciona "HelloWorld" como "Target".



     Dale al botón de "Play", ahora tendrías que ver el texto de "Hello World" girando.



 

Tercer ejemplo, una novela visual.

Objetivos:

  1. Carga dinámica de recursos en escena.
  2. Creación de librerías DLL. 
  3. Uso de .gdignore. 
  4. Reemplazo de tipo de componentes. 
  5. Separación por capas (front, back, datos).
  6. Introducción a los métodos _Ready e _Input.

 

    Crea un nuevo proyecto y llámalo VisualNovelWithDll (por ejemplo). En la pestaña de "Escenas" selecciona una escena en 2D y añade al Node2D un CanvasLayer. A ese CanvasLayer, añádele a su vez un un TextureRect (con ajuste de anclaje "completo", para que ocupe todo el área) y dos labels (uno para el nombre del personaje y otro para su diálogo). Edita las propiedades de las labels para cambiar el tamaño de sus fuentes (por ejemplo 24 para el nombre el NPC y 32 para el diálogo) y juega con los ajustes de anclaje para dejar el layout a tu gusto. Edita también la propiedad "Expand mode" del TextureRect a "Fit Width Proportional", para que las imágenes que le carguemos luego pueden ser re-escaladas de forma proporcional.


     En el navegador de recursos, crea una carpeta "Assets" y dentro de ella otra llamada "Backgrounds". En ella pondremos tres imágenes, una donde veamos un escenario vacío, otro con un primer plano de un personaje feliz y otro donde el mismo personaje se vea triste. Deben de llamarse respectivamente: 001.png, 002.png y 003.png. Igual que antes, si no puedes dibujarlos, puedes recuperarlos de mi GitHub para hacer el ejercicio (https://github.com/LeHamsterRuso/Godot4.NetExamples/tree/master/003---visualnovelwithdll/Assets/Backgrounds). Crea también una carpeta Scripts y añade en ella un nuevo script de tipo C# llamado "Dialogs.cs". La arborescencia del proyecto debería de quedarte así:


 

     Abre VSCode, accede al explorador (el icono de los dos documentos a la izquierda y clica en "Crear proyecto de .NET".

 

 

    En la ventanita que se te abrirá empieza a teclear "biblioteca" y selecciona "Biclioteca de clases". Acto seguido selecciona un subdirectorio de tu proyecto Godot donde guardarla, asígnale el nombre DLL y selecciona "crear un nuevo proyecto".


     Una vez creado, en la raíz de la carpeta dll crea un fichero vacío llamado ".gdignore". Esto hará que el editor de Godot ignore todos los ficheros que añadas en tu carpeta de DLL.


    En nuestra DLL, crearemos una clase llamada "Dialog.cs" que contendrá únicamente 3 strings: Uno para definir el fondo de imagen, otro para definir el nombre de quién habla y otro para mostrar el texto que dirá.


 

     También crearemos una clase principal que se llamará Game, de naturaleza estática, y que contendrá una lista de diálogos (para hacerla puedes renombrar la clase Class1 que se crea por defecto):


     Si os fijáis en el código de la clase, esta dll realmente carga los datos a mostrar y gestiona la lógica de navegación entre diálogos. Ahorra cierra el VSCode, vuelve a Godot y abre el script de Dialogs.cs que habíamos creado. Verás que la arborescencia del explorador del VSCode ha cambiado y que ahora te ha abierto directamente la carpeta donde tienes todo el proyecto de Godot. Ahí deberías de identificar dos ficheros "csproj": El fichero principal del juego y el de la DLL que acabamos de crear. Edita el primero y haciendo uso de las primitivas "ItemGroup" y "ProjectReference", añade manualmente una dependencia de la DLL en el proyecto principal. Debería de quedarte algo así, en función de cómo hayas montado la arborescencia:

   De hecho, si os fijas cómo VSCode ha creado el proyecto DLL, la organización queda un poco fea (con un subdirectorio DLL donde metemos las clases. Podemos jugar con la arborescencia a nuestro gusto, siempre que actualicemos los "csproj" en consecuencia y que manejemos con coherencia los namespaces de nuestras clases.

    En esa captura he desplazado el contenido DLL/DLL en DDL/ y he actualizado el csproj raíz para apuntar a la ruta actualizada del csproj de la DLL.

     Para verificar que no hemos roto nada, puedes hacer uso de la terminal de VSCode para compilar la solución y la DLL a través del comando "dotnet build":


 

    Bueno, ya falta poco. Ahora ya puedes usar tu DLL en el proyecto de Godot... De hecho, como la DLL sale referenciada en el fichero csproj raíz, para hacer uso de sus clases nos bastará con utilizar el "using DLL" en la cabecera de nuestros scripts.

    Ahora abre el fichero "Scripts> Dialogs.cs" que habíamos creado antes, dentro de Godot, y edítalo para recuperar los tres componentes que hemos empleado en nuestra escena (un texturerect y dos labels) y alimentarlos en función de la lógica que hemos implementado en nuestra DLL. Para hacer eso tenemos dos métodos de Godot que nos serán útiles: El método _Ready, que se lanza al arrancar una escena, y el método _Input, que se lanza cuando se detecta alguna interacción por teclado, ratón o gamepad.

    De hecho, lo que haremos será crear una función FillScreen que se encargará de alimentar los labels y el texture rect y llamaremos a ésta desde los métodos _Ready (para acceder al primer diálogo) y _Input (para ir avanzando en los diálogos).


    Vuelve a Godot, dale al icono de la llave inglesa para compilar, arrastra el script al nodo raíz y asocia los dos labels y el texture rect al script:



 

     Dale a "Play", deberías de ver la siguiente escena en bucle (al ir al último diálogo volvemos al primero):





 

    Volvamos a nuestro script y aplica los siguientes cambios:


     Ahora si te fijas el comportamiento cambia, el texto se va mostrando letra a letra, como en una novela visual comercial:



 

    Ahora vamos a aplicar un fondo básico a nuestro texto. Para ello duplica los dos labels que tenemos (selecciona uno, cópialo en el portapapeles, ve al nodo raíz y pégalo... y haz lo mismo con el otro). Acto seguido selecciona una de las copias y haciendo clic derecho cambia su tipo a "ColorRect". Esto nos permite, de forma simple, clonar las coordenadas y dimensiones del componente original.




     Ahora, a través del inspector, asigna a tus dos ColorRect un color azulado con un valor de Alpha (letra A) cercano al 80, según tu gusto. El "alpha" es la capa de transparencia del componente, un valor cercano al 0 lo convierte en invisible y el valor 255 (el máximo) lo convierte en opaco. Un valor entre el 0 y el 255 significa que nuestro componente será más o menos transparente (el nivel de transparencia depende de si está más cercano al valor 0 o al 255).

 

    Acto seguido desplaza los dos labels dentro de sus respectivos ColorRects: Esto nos permitirá que el texto se vea por encima del fondo. En caso contrario, el texto nos saldría azulado, debido a que la capa de transparencia se le estaría aplicando por encima.

 

     Dale a play, ahora los textos deberían de verse con nuestro fondo básico:


 

    Ahora vamos a reorganizar la DLL para separar la lógica del modelado de datos. La idea será que nuestra DLL pase a llamarse "Core" y crear una segunda DLL que llamaremos "Data". Esto nos permitirá, por ejemplo, separar el motor del modelo de datos, haciendo que el día de mañana sea más fácil migrar nuestro sistema a una base de datos o a un sistema de carga por archivos (json, csv, etc).

     Para ello, vamos a duplicar la carpeta DLL (copiar+pegar en el explorador de ficheros), renombraremos el original a Core, renombraremos la copia a Data, tocaremos los tres csproj para corregir la nueva arborescencia, borraremos el Dialog.cs del proyecto Core, borraremos Game.cs de Data y actualizaremos los namespaces de ambos proyectos. Como Core dependerá de Data, tendremos también que referenciarlo en su csproj y hacer uso de "Using Data;" en la clase Game.cs.








 

      Y ahora, para acabar con la entrada, nos queda sólo sanear la clase Game.cs de la dll "Core". Si nos fijamos, en ella hacemos una iniciación de datos mockeados, lo ideal sería llevarnos esa iniciación en la librería "Data", puesto que en el futuro queremos recuperar datos a través de algún tipo de datasource, hacia un fichero físico o una base de datos.

     Para facilitar esta tarea de migración, atentos a la jugada, nos crearemos una clase estática Mock dentro de la DLL "Data", que contendrá por ahora nuestro guion. 

 
 
     No obstante, nuestra clase Game no accederá directamente al Mock, si no a través de una clase intermedia llamada Data, que hará uso del mock como si fuera un "data source" (fichero fuente). Así, el día de mañana, cuando tengamos nuestros dto y esquemas bien montados, bastará con apuntar a otra clase estática (bastaría con cambiar el tipo Mock a otro).
 




     Por último compila y verifica que no hayamos roto nada. Espero que hayas disfrutado con esta entrada. ¡Hasta la próxima!