Hola CUBO en C-script


 


En este artículo daremos una introducción a la programación del motor gráfico 3DGS A6, enfocándonos principalmente en su lenguaje de programación C-script.


Así que quieres hacer juegos pero no quieres aprender OpenGL ni DirectX?. Bueno entonces quizás 3DGS A6 pueda ayudarte.

Si ya sabes C++ yo te recomendaría aprender a usar otros motores gráficos: Irrlicht (es el mas fácil) y Ogre 3D (mas difícil pero mucho mas completo) ambos son motores de código abierto a diferencia del 3DGS. Sin embargo con 3DGS puedes hacer algunas cosas más fácilmente, de cierta forma me recuerda mucho a Visual Basic y la programación de aplicaciones para Win32. ¿Te acuerdas? Hubo un tiempo en que los verdaderos programadores eran sólo aquellos que programaban usando WinMain y callbacks (en C por supuesto), también estaba esa cosa lenta y fofa llamada MFC que usaban los programadores que se ganaban los frejoles hasta que apareció VB y cualquiera pudo hacer su formulario en dos minutos. (Dímelo a mi que aprendí a hacer mis controles ActiveX simplemente siguiendo los tutoriales) Salvando las distancias (3DGS no es tan fácil ni esta tan bien documentado como el VB) con el A6 puedes crear mundos virtuales, cargar modelos y animarlos, usando relativamente muy poco código Algo que te llevaría bastante tiempo hacer en DirectX. Pero, y aquí viene el pero, si quieres hacer algo que va mas allá de sus caracterisiticas originales de diseño, te puede costar mucho (y digo mucho) trabajo hacerlo. Te habla la experiencia :-) Si quieres mi opinion personal sólo te lo recomendaría para juegos simples o paseos virtuales.

 

Esto es lo que necesitas:

 

1.- El instalador (demo de 30 días) se puede descargar de Conitec

2.- El nivel de prueba: hola.zip

 

Primero instala el 3DGS. (Este tutorial fue probado usando la versión 6.31.4 Trial). Descomprime luego el proyecto demo hola.zip en una carpeta. Esta será la CARPETA DEL PROYECTO, toda imagen, modelo o script del proyecto deberá encontrarse aquí. Busca los accesos directos creados por el instalador e inicia el GameStudio (recuerda que necesitas DirectX 9.0c). Se cargará el WED que viene a ser el EDITOR DE NIVELES del 3DGS. Desde el menú [File]->[Open...] del WED abre el archivo HOLA.WMP (que esta dentro de la carpeta del proyecto), una vez cargado el nivel esto es lo que debes ver:

 

mandel_1

Bueno ahora un poco de explicación, el archivo WMP que cargaste contiene el NIVEL (también conocido como escenario), en este caso sólo contiene una simple caja como puedes ver (silueta de líneas blancas), los símbolos amarillos son luces del nivel, el símbolo verde con forma de cámara es (adivinaste) la posición inicial donde va a aparecer la cámara, y la caja azul nuestro modelo (otro lindo cubito) que hará de ENTIDAD PRINCIPAL de nuestro programa. (NOTA: este no es un tutorial de como crear niveles usando el WED, si quieres puedes jugar con el nivel demo agregándole mas cajas y asignándoles texturas, te darás cuenta de que no es tan difícil).

 

Un NIVEL es simplemente la geometría que carga el motor gráfico y gracias al efecto de luces, texturas y otros artilugios te da la ilusión de ser un mundo virtual. Una ENTIDAD es un objeto que se carga dentro del nivel y que gracias a las ACCIONES o funciones que la controlan te da la ilusión de estar animada.

 

Continuemos, en la programación normal el típico programa HOLA MUNDO se contentaba con escribir esa frase en pantalla, que fácil, suerte que para nosotros sea diferente (bienvenido a la programación 3D...) aquí las cosas no son tan simples, nuestro HOLA CUBO tiene que ser capaz de:

 

1) Cargar un nivel (escenario)

2) Animar una entidad (modelo o actor)

3) Controlar el programa (usando el teclado y/o ratón)

 

Sip. Todo eso en un simple programa. Pero antes de continuar quiero que veas como debe lucir nuestro programa cuando este acabado. Para ello debes COMPILAR EL NIVEL, eso se hace desde el menú [File]->[Build WMB...] del WED. Te saldrá el dialogo siguiente:

 

mandel_1

Fíjate que debe estar seleccionado (hundido) el botón de [Build Level], presiona el botón de [OK] y luego de unos segundos te saldrá esto:

 

mandel_1

Presiona [OK] y ya tienes tu nivel compilado, recién ahora podrás usar tu nivel.

Haber mas despacio por favor... Bueno. El archivo WMP que cargaste al principio sólo contenía la descripción del nivel (las cosas que van en el nivel) y es sólo un archivo de texto, ábrelo con el Notepad y verás su contenido. Pero el motor gráfico (el ACKNEX.EXE que esta dentro de la carpeta BIN de la carpeta donde instalaste el 3DGS) necesita el nivel compilado y no la descripción del nivel. El WMP es útil a la hora de diseñar tu nivel, y sólo le sirve al WED pero para poder usar ese nivel necesitas el archivo HOLA.WMB (el nivel compilado) que debe haberse generado en la carpeta donde estas trabajando. Este archivo WMB tiene la geometría del nivel + las texturas del nivel + la iluminación del nivel organizado de una manera que sea fácil de usar por el motor gráfico. No confundas motor gráfico (ACKNEX) con editor de niveles (WED) ni descripción de nivel (WMP) con nivel compilado (WMB). Este archivo WMB es binario y si entiendes lo que contiene seguro que ya debes haberte hecho tu propio motor gráfico hace años. Del panel de la izquierda del WED selecciona la pestaña [Resources] como se muestra:

 

mandel_1

Aquí WED te muestra todos los elementos cargados en tu nivel. Dale doble click al CUBO.MDL que he marcado como MODELOS, te saldrá el hermano menor del WED: el MED

 

mandel_1

El MED es el editor de modelos del 3DGS y lo he usado para crear el CUBO.MDL pero ni este es un tutorial del MED ni soy yo un diseñador de modelos medianamente decente así que no tengo mucho que decir. Una advertencia: MED no es muy poderoso y sólo sirve para crear modelos sin muchas pretensiones.

Ahora sigamos, en el WED dale doble click al script HOLA.WDL que he marcado como SCRIPTS y verás al último y mas reciente miembro de la familia 3DGS: el editor de scripts SED

 

 

mandel_1

Vamos a desarrollar este tutorial usando el SED (pero puedes configurar tu entorno de trabajo para emplear cualquier otro editor, yo prefiero el EDITPLUS). Antes de cargar el programa debes decirle a SED donde se encuentra el 3DGS y cual es el script principal de tu proyecto, para ello (desde el SED) selecciona el menú [Options]->[Configuration], te saldrá el cuadro siguiente:

 

mandel_1

Fíjate como he llenado las cajas de texto con la ruta de instalación del 3DGS y la ruta del script HOLA.WDL (que esta en la carpeta del proyecto). También he seleccionado la opción [Window] para ver mi programa en modo ventana. Presiona [OK] y de regreso en el SED selecciona el menú [Debug]->[Test Run] (o la tecla [F5]).

Verás un lindo cubito rotando, unos signos de flechas izquierda y derecha y un fondo de ladrillos (jeje... como se nota que no diseño niveles). Si le haces click a las flechas en pantalla el cubo girará en esa dirección y si ya esta girando en ese sentido girará mas rápido aun, puedes moverte usando las teclas direccionales:

 

mandel_1

Si no puedes ver esto es que algo anda mal, no tienes DirectX 9.0c, no compilaste el nivel o no ingresaste bien las rutas. Revisalo todo de nuevo.

 

En caso de que todo vaya bien FELICIDADES por tu primer programa en C-script, aquí termina este corto tutorial...

 

Nah, recién estamos comenzando. (Con tantos dibujitos esto debió llamarse 3DGS for dummies...). Ahora viene lo interesante, revisemos el código de HOLA.WDL paso a paso. ¿Te acuerdas de nuestro primer objetivo? CARGAR UN NIVEL, eso lo puedes hacer fácilmente de este modo:

 


//---------------------------------------------------------------               (1)
// (1) Código necesario parar cargar un nivel                                   (2)
//---------------------------------------------------------------               (3)
string  gs_nombre_nivel = <hola.wmb>;    // nombre del nivel a cargar           (4)

function main ()            // funcion inicial y principal                      (5)
{
    warn_level = 2;         // para indicar malas texturas y código             (6)

    wait (3);               // esperar 3 frames (para el triple buffering)      (7) 

    level_load (gs_nombre_nivel);        // cargar el nivel...                  (8)
}

 

Las primeras tres líneas son líneas de comentario. Los comentarios de línea comienzan con //, todo lo que sigue a continuación de la línea no es interpretado por el motor gráfico (puedes escribir lo que quieras allí), recuerda que es muy importante comentar el código En la línea (4) declaramos una variable cadena (tipo string) esa cadena la inicializamos con el nombre de nuestro nivel (¿Te acuerdas de HOLA.WMB?), esta cadena también pudo haber sido declarada de esta forma:

string gs_nombre_nivel = "hola.wmb";

Usando comillas "" pero usar las llaves <> tiene sus ventajas a futuro (cuando la cadena hace referencia a archivos o mapas de bits). Si te has dado cuenta da lo mismo escribir en mayúsculas como en minúsculas (a diferencia de C) entonces daba lo mismo escribir:

STRING GS_NOMBRE_NIVEL = <HOLA.WMB>;

También te darás cuenta que estoy usando el prefijo "gs_" antes del nombre de la variable. La "g" es por que esta variable es global (visible para todos) y la "s" por que es una cadena. Es mejor tener buenos hábitos desde el principio, a mi particularmente no me gustan las declaraciones tipo:

string cad1 = "hola.wmb";

En la línea (5) comienza la funcion principal del programa, sólo puede haber un único main () en todo el programa.

En la línea (6) le decimos al motor gráfico que nos informe de posibles errores en el nivel o el código. Este warn_level es una variable predefinida, es decir que no necesitas declararla pues ya viene declarada (hay un montón de este tipo de variables). Con warn_level = 0; desabilitamos las advertencias y con warn_level = 2; queremos todas las advertencias posibles. Es bueno incluir esta línea de código al inicio de nuestros proyectos.

en la línea (7) encontramos la que es tal vez la instrucción mas útil y especial del C-script: wait (3); esta es una funcion predefinida por el motor gráfico (también hay un montón de este tipo de funciones). Lo que hace es detener la funcion que la llamó por un numero de frames (mientras el motor gráfico sigue refrescando la pantalla y/o ejecutando otras funciones) y luego de esperar el numero de frames indicado pone a andar de nuevo la funcion detenida. Los frames no son otra cosa que las pantallas dibujadas por el motor gráfico mientras esta en ejecución. Si tu programa corre a 30 frames por segundo (30 FPS) significa que tu motor gráfico esta refrescando (dibujando) la pantalla 30 veces por segundo, eso te da la impresión de movimiento. Recuerda que desde ahora para nosotros el tiempo se mide en frames. Todo el código que hagamos se ejecutará en el intermedio (durante el tiempo que el motor gráfico se esta preparando para mostrar la siguiente pantalla). En nuestro caso sólo esperamos tres frames (para que el motor termine de despertarse) y continuamos con la funcion main (). Seguiremos hablando de wait () mas adelante.

La línea (8) es la que carga nuestro nivel compilado. La funcion predefinida level_load () hace eso. Esta funcion recibe como argumento una cadena con el nombre del nivel compilado y se encarga de cargar ese nivel.

 

Vamos a probar lo que tenemos hasta ahora, para ello haz primero una copia del script HOLA.WDL (por si lo necesitas luego), abre el SED, borra todo el contenido original de HOLA.WDL, copia el código anterior, salva el archivo y ejecutalo como antes (pulsa la tecla [F5]). Te saldrá un mensaje diciéndote que no se encontró algo, ignóralo y dale a [Aceptar]. Veras que se carga el nivel, pero el cubo ya no rota, no hay puntero de ratón ni te puedes mover usando el teclado. Y es lógico, no has puesto nada de código para hacer eso. Sin embargo hay un truco para poder ver el nivel aún en estos casos: presiona la tecla [0] (numero cero) del teclado NO NUMERICO (el que esta encima de las letras) y podrás moverte usando las flechas y el ratón.

 

Ya sabemos como cargar niveles, ahora sigamos con el siguiente objetivo, ANIMAR UN MODELO. En este ejemplo el modelo ya esta cargado en el nivel. (Puedes cargar otros modelos usando el menú [Object]->[Load Entity...] del WED). Si quieres que el modelo haga algo (rote, por ejemplo) se necesita asignarle una ACCION. Las acciones son aquellas funciones que controlan a las ENTIDADES (es decir nuestro cubo). Hay dos métodos de asignar acciones, aquí te mostrare sólo la mas simple, desde el WED activa la pestaña [Objects] y selecciona el modelo [cubo], dale click derecho para que te salga el menú contextual y selecciona el menú [Properties]:

 

mandel_1

Te saldrá un cuadro con información del objeto, selecciona la pestaña [behaviour], deberías ver esto:

 

mandel_1

Aquí el cubo ya tenia asignada la acción rotar_cubo (por eso rotaba anteriormente). Aquí debes escribir el nombre de la acción que controlará la entidad, déjalo como esta. De vuelta en el SED vamos a agregar la acción que hará que nuestro cubo rote, copia el siguiente código y pegalo al INICIO del script, salva el archivo y ejecutalo. No debe aparecer ningún mensaje y el cubo debe aparecer rotando:

 


//---------------------------------------------------------------
// (2) Código necesario parar animar el cubo
//---------------------------------------------------------------
var     gn_inc_rot = 1;     // variable global para controlar el sentido        (1)
                            // y velocidad de rotacion del cubo

action rotar_cubo           // esta es la manera de declarar una acción         (2)
{
    if (my != null)     // es importante verificar que el puntero no sea NULL   (3)
    {
        while (1)       // bucle infinito                                       (4)
        {
            my.pan = my.pan + gn_inc_rot;   // cambiamos el angulo (rotamos)    (5)
            wait(1);    // dejamos que el motor dibuje en pantalla              (6)
        }
    }
}

No hay nada de magia aquí, para rotar nuestro cubo simplemente hicimos variar el angulo XY del objeto (ese angulo en C-scrip se llama pan). El motor gráfico carga el modelo y ve si tiene asociado alguna acción, busca esa acción por su nombre en el script y cuando la encuentra comienza a ejecutarla como cualquier otra funcion. Puedes pensar entonces que la ACCION ha tomado el control de esa ENTIDAD.

En la línea (1) declaramos una variable global y la inicializamos a 1. Esta variable no es realmente necesaria aún pero ya verás su utilidad mas adelante. En C-script no existen variables de tipo int o float o long sólo var, estas variables almacenan números con 3 decimales.

En la línea (2) declaramos la acción rotar_cubo (es necesario que sea el mismo nombre que aparecío en el WED). Si cambias este nombre tienes que cambiar también el nombre de la acción que aparece en el WED y VOLVER A COMPILAR EL NIVEL. Dejalo así por ahora.

La línea (3) verifica que la entidad sea válida (no nula). Esto lo hace la instrucción: if (my != null) que significa: si la entidad de esta acción (my) es diferente (!=) de nulo (null), continua, de lo contrario no hagas nada. La variable predefinida my permite acceder a la entidad que es controlada por la acción, es muy importante que te des cuenta que en este caso my hace referencia a nuestro cubo.

La línea (4) da inicio a un bucle infinito: while (1). (Recuerda que para llegar aquí my es diferente de nulo y es nuestro cubo.). Este bucle infinito se va a ejecutar mientras nuestro programa este activo. Aquí vamos a hacer que el cubo rote.

La línea (5) indica como:

my.pan = my.pan + gn_inc_rot;

En C-script cuando quieres referirte a la propiedad de algún objeto o entidad debes usar la notación [objeto].[propiedad], De este modo my.pan se refiere a la propiedad pan de la entidad my.

Si la entidad tenia un angulo pan de cero, después de esta instrucción va a tener una angulo pan de gn_inc_rot. Como gn_inc_rot es inicializado a 1, es fácil deducir que esta instrucción lo que va a hacer es aumentar el valor del angulo pan del cubo (my.pan) de uno en uno. Te advierto que los ángulos en C-script se escriben en grados sexagesimales (un vuelta va de 0 a 360). Pregunta de trigonometría de colegio: ¿Que pasa cuando el angulo aumenta? pues que gira en sentido antihorario. ¿Y que pasa si disminuye? pues gira en sentido horario. Mas fácil no puede ser. Lo mismo le esta pasando a nuestro cubo.

La línea (6) tiene un wait (1) que permite que el motor dibuje un frame y luego continúe con el bucle infinito. Este wait (1) es importantísimo, te reto a que lo borres y veas que pasa... Ves?, te sale un mensaje de error, de posible bucle sin fin y una pantalla negra... ups. Con el wait (1) aunque estamos dentro de un bucle infinito, en realidad no estamos dentro de un bucle infinito continuo, sino que cada vez que el motor llega a la instrucción wait (1) congela la funcion actual (una acción es sólo un tipo de funcion especial) y se pone a ejecutar otras funciones o dibujar en la pantalla (si no hay nada mas que hacer), de este modo múltiples funciones con bucles infinitos pueden coexistir pacíficamente dentro del mismo programa... Interesante no?. Una funcion podría controlar la animación de las entidades, otra el movimiento, otra su inteligencia artificial, etc, etc. Por eso vuelve a poner ese wait (1) de donde lo sacaste!!

 

Ahora el paso tres: CONTROLAR EL PROGRAMA USANDO EL TECLADO Y/O RATON. Con el teclado vamos a controlar la cámara para ello debes agregar el siguiente código al INICIO del script:

 


//---------------------------------------------------------------
// (3) Código para controlar cámara con teclado
//---------------------------------------------------------------
function camera_move ()
{
    while (1)
    {
        if (key_home)       {camera.tilt += 1;}
        
        if (key_end)        {camera.tilt -= 1;}
        
        if (key_pgup)       {camera.z += 1;}
        
        if (key_pgdn)       {camera.z -= 1;}

        if (key_cuu)        {camera.x += 1;}
        
        if (key_cud)        {camera.x -= 1;}
        
        if (key_cul)        {camera.y += 1;}
        
        if (key_cur)        {camera.y -= 1;}
        
        wait(1);
    }
}

Esta es otra funcion estilo a mi nadie me para (es decir que también se ejecuta indefinidamente en un bucle continuo), vas a encontrar muchas funciones de este tipo. La variable predefinida camera viene a ser la cámara principal del programa, su posición y su angulo define lo que ves en la pantalla (técnicamente hablando es un objeto del tipo VISTA y puede haber mas de uno en un mismo programa). Entonces camera.x es la posición x de la cámara, del mismo modo camera.y es la posición y de la cámara, etc, etc. (Todo este x, y, z debe recordarte las coordenadas cartesianas y porque no debías dormirte en las clases de matematica de tu colegio). El angulo tilt es el angulo de elevación con respecto al plano XY (el suelo). Es fácil pensar que el angulo pan hace girar un objeto de derecha a izquierda, mientras el angulo tilt hace que gire de abajo a arriba.

Las variables predefinidas key_home, key_end, ... son establecidas a on (1) por el motor si el usuario esta presionando las tecla correspondientes:

key_home    [INICIO]
key_end     [FIN]
key_pgup    [PAGINA ARRIBA]
key_pgdn    [PAGINA ABAJO]
key_cuu     [ARRIBA]
key_cud     [ABAJO]
key_cul     [IZQUIERDA}
key_cur     [DERECHA]
				

Entonces lo que hace la funcion camera_move () es simplemente ver si alguna de esas teclas se ha presionado y en caso de que sea así, alterar alguna propiedad de la cámara. Por ejemplo si presionas la tecla [INICIO] la instrucción: if (key_home) se hace verdadera y se ejecuta el código correspondiente, que es:

camera.tilt += 1;

que es una forma simplificada de decir:

camera.tilt = camera.tilt + 1;

y eso hace que la cámara gire hacia arriba (pues estamos aumentando el angulo de elevación). El wait (1) al final nos permite ejecutar otras funciones (en este caso la acción rotar_cubo) y dibujar la pantalla.

Sólo falta modificar la funcion main () para cargar nuestra funcion al inicio:

 


//---------------------------------------------------------------
// (4) Código modificado para controlar cámara
//---------------------------------------------------------------
function main ()
{
    warn_level = 2;         // para indicar malas texturas y código wdl

    wait(3);                // esperar 3 frames (para el triple buffering)

    level_load (gs_nombre_nivel);   // cargar el nivel...
    wait(2);                // ESPERAR que el nivel se cargue                   (1)
        
    camera_move ();         // mover teclado                                    (2)
	
}

En la línea (1) esperamos que se termine de cargar el nivel.

En la línea (2) llamamos a la funcion que se encargara de sensar el teclado y mover la cámara. Nota que en este momento ya tenemos dos funciones de duración indefinida ejecutándose simultáneamente, una es la funcion camera_move () y otra la acción rotar_cubo , (no exactamente simultáneas pues el motor gráfico sólo ejecuta una instrucción a la vez, pero si simultáneas en el sentido de que ambas comparten el tiempo que el motor gráfico ha destinado para ejecutar las funciones del script).

 

Para controlar el ratón necesitamos una mapa de bits que hará de puntero: ARROW.BMP (esta en la carpeta del proyecto) y la siguiente función que deberás pegar al INICIO de tu script:

 


//---------------------------------------------------------------
// (5) Código para controlar el ratón
//---------------------------------------------------------------
bmap bmp_mouse          = <arrow.bmp>;  //  mapa de bits    (1)

function mouse_move ()                              
{
    mouse_mode = 2;         // mouse visible                (2)
    mouse_map = bmp_mouse;  // asignamos bmp                (3)
    
    while(1)
    {
        mouse_pos.x = pointer.x;        // movemos mouse    (4)
        mouse_pos.y = pointer.y;        // " "              (5)
        wait(1);
    }
}

En la línea (1) declaramos un mapa de bits (bmap) y lo inicializamos con el archivo que tenemos en la carpeta del proyecto.

En la línea (2) hacemos el puntero del ratón visible, si le asignamos cero se volverá invisible.

En la línea (3) establecemos como puntero del ratón la imagen que hemos cargado anteriormente. (Si quieres cambiar de imagen, copia tu imagen a la carpeta del proyecto y cambia la línea (1) con el nombre correspondiente de tu archivo.)

Las líneas (4) y (5) refrescan la posición del puntero del ratón sobre la pantalla. También esta es una función que corre indefinidamente. Date cuenta de lo simple que es cargar una imagen y usarla. El código anterior esta lleno de variables predefinidas que debes investigar una por una en la ayuda. (El SED muestra una ayuda en la parte inferior que te da pistas de la palabra en la que se encuentra el cursor). Sólo falta alterar la funcion main () para incluir esta funcion:


//---------------------------------------------------------------
// (6) Código modificado para controlar ratón
//---------------------------------------------------------------
string  gs_nombre_nivel = <hola.wmb>;    // nombre del nivel a cargar

function main()                                 // funcion inicial y principal
{
    warn_level = 2;         // para indicar malas texturas y código wdl

    wait(3);                // esperar 3 frames (para el triple buffering)

    level_load (gs_nombre_nivel);   // cargar el nivel...
    
    wait(2);                // ESPERAR que el nivel se cargue
        
    camera_move ();         // mover teclado
    mouse_move ();          // mover mouse
}

Prueba tu script, te darás cuenta que ahora puedes mover la cámara con las teclas que definiste y cuando mueves el ratón, el puntero se mueve también. Sólo nos falta controlar el sentido del giro del cubo con unos botones.

 

En C-script hacer botones simples no es problema, sólo necesitamos definir un PANEL y asignarle las acciones correspondientes, para ello agrega el siguiente código al FINAL de tu script:

 

 


//---------------------------------------------------------------
// (7) Código para definir el panel de control
//---------------------------------------------------------------
bmap bmp_derecha        = <fd.bmp>;     // imágenes de los botones              (1)
bmap bmp_izquierda      = <fi.bmp>;     // " "                                  (2)
bmap bmp_derecha_on     = <fd_on.bmp>;  // " "                                  (3)
bmap bmp_izquierda_on   = <fi_on.bmp>;  // " "                                  (4)

panel panel_control                     // definicion de panel                  (5)
{
    button = 100, 350,                                      // posición x,y     (6)
             bmp_izquierda, bmp_izquierda, bmp_izquierda_on,// imágenes         (7)
             girar_izquierda, null, null;                   // acciones         (8)

    button = 500, 350,
             bmp_derecha, bmp_derecha, bmp_derecha_on,
             girar_derecha, null, null;

    flags = d3d, visible;               // para que el panel sea visible        (9)
}

En las cuatro primeras líneas declaramos las imágenes de nuestros botones (dos por cada uno). Las líneas (1) y (2) son para las imágenes que mostrarán normalmente los botones, las líneas (3) y (4) son para las imágenes que mostraran cuando el puntero del ratón este sobre ellas (haran el efecto de encendido de los botones).

La línea (5) define el panel de nombre panel_control. Un panel es como un contenedor y los botones se definen dentro del panel, las líneas del (6) al (8) definen el primer botón:

La línea (6) establece la posición (en pixeles) donde aparece el botón, esta posición se mide desde la esquina superior izquierda.

La línea (7) establece las imágenes que tendrá el botón.

La línea (8) nos permite indicar que funcion se ejecutará cuando se haga click en el botón en este caso la funcion girar_izquierda. (Los parámetros que pasamos como null no son importantes en este ejemplo).

Sólo nos falta definir la funcion girar_izquierda y girar_derecha. Copia el siguiente código al FINAL del script:

 


//---------------------------------------------------------------
// (8) Funciones para los botones
//---------------------------------------------------------------
function girar_izquierda()
{
    if (gn_inc_rot < 0)
    {
        gn_inc_rot = 1;
    }
    else
    {
        gn_inc_rot += 1;        
    }
}

function girar_derecha()
{
    if (gn_inc_rot > 0)
    {
        gn_inc_rot = -1;        
    }
    else
    {
        gn_inc_rot -= 1;
    }
}

¿Te acuerdas de la variable gn_inc_rot? Pues aquí esta de nuevo. Y ahora si pone en evidencia sus verdaderas intenciones. Si recuerdas el código para hacer rotar el cubo era:

my.pan = my.pan + gn_inc_rot;

Cuando haces click en el botón de la izquierda, este llama a la funcion girar_izquierda (). Analizando esta funcion vemos que hay dos casos, si gn_inc_rot no es negativo (como al principio donde vale 1) entonces se ejecuta la instrucción else del if, esto es:

gn_inc_rot += 1; (que es lo mismo que: gn_inc_rot = gn_inc_rot + 1;)

Esta instrucción hace que el valor de gn_inc_rot se incremente en uno. Esto afecta la expresión que hace girar el cubo: my.pan = my.pan + gn_inc_rot;

Esta expresión está incrementando ahora el angulo pan con un valor mas grande (esto hace que el cubo gire mas rápido en sentido antihorario).

Por el contrario si el valor de gn_inc_rot es negativo (puede ser negativo porque la funcion rotar_derecha la pone en -1 cuando se le hace click y gn_inc_rot es positivo), la funcion rotar izquierda simplemente volverá a poner el valor de gn_inc_rot en 1, como al principio. Analiza estas funciones y te darás cuenta que lo que hacen es simplemente cambiar el sentido de giro del cubo (si este estaba moviéndose en el otro sentido), o acelerar la velocidad de giro (si ya estaban moviéndose en el sentido elegido).

 

Ejecuta el programa y verás como puedes controlar el sentido de giro del cubo con estos botones. Así es como debe haber quedado tu script:

 


//---------------------------------------------------------------
// (5) Código para controlar el ratón
//---------------------------------------------------------------
bmap bmp_mouse          = <arrow.bmp>;  //  mapa de bits    (1)

function mouse_move ()                              
{
    mouse_mode = 2;         // mouse visible                (2)
    mouse_map = bmp_mouse;  // asignamos bmp                (3)
    
    while(1)
    {
        mouse_pos.x = pointer.x;        // movemos mouse    (4)
        mouse_pos.y = pointer.y;        // " "              (5)
        wait(1);
    }
}

//---------------------------------------------------------------
// (3) Código para controlar cámara con teclado
//---------------------------------------------------------------
function camera_move ()
{
    while (1)
    {
        if (key_home)       {camera.tilt += 1;}
        
        if (key_end)        {camera.tilt -= 1;}

        
        if (key_pgup)       {camera.z += 1;}
        
        if (key_pgdn)       {camera.z -= 1;}

        if (key_cuu)        {camera.x += 1;}
        
        if (key_cud)        {camera.x -= 1;}
        
        if (key_cul)        {camera.y += 1;}
        
        if (key_cur)        {camera.y -= 1;}
        
        wait(1);
    }
}

//---------------------------------------------------------------
// (2) Código necesario parar animar el cubo
//---------------------------------------------------------------
var     gn_inc_rot = 1;     // variable global para controlar el sentido        (1)
                            // y velocidad de rotacion del cubo

action rotar_cubo           // esta es la manera de declarar una acción         (2)
{
    if (my != null)     // es importante verificar que el puntero no sea NULL   (3)
    {
        while (1)       // bucle infinito                                       (4)
        {
            my.pan = my.pan + gn_inc_rot;   // cambiamos el angulo (rotamos)    (5)
            wait(1);    // dejamos que el motor dibuje en pantalla              (6)
        }
    }
}

//---------------------------------------------------------------
// (6) Código modificado para controlar ratón
//---------------------------------------------------------------
string  gs_nombre_nivel = <hola.wmb>;    // nombre del nivel a cargar

function main()                                 // funcion inicial y principal
{
    warn_level = 2;         // para indicar malas texturas y código wdl

    wait(3);                // esperar 3 frames (para el triple buffering)

    level_load (gs_nombre_nivel);   // cargar el nivel...
    
    wait(2);                // ESPERAR que el nivel se cargue
        
    camera_move ();         // mover teclado
    
    mouse_move ();          // mover mouse                                      (1)    
}


//---------------------------------------------------------------
// (7) Código para definir el panel de control
//---------------------------------------------------------------
bmap bmp_derecha        = <fd.bmp>;     // imágenes de los botones              (1)
bmap bmp_izquierda      = <fi.bmp>;     // " "                                  (2)
bmap bmp_derecha_on     = <fd_on.bmp>;  // " "                                  (3)
bmap bmp_izquierda_on   = <fi_on.bmp>;  // " "                                  (4)

panel panel_control                     // definicion de panel                  (5)
{
    button = 100, 350,                                      // posición x,y     (6)
             bmp_izquierda, bmp_izquierda, bmp_izquierda_on,// imágenes         (7)
             girar_izquierda, null, null;                   // acciones         (8)

    button = 500, 350,
             bmp_derecha, bmp_derecha, bmp_derecha_on,
             girar_derecha, null, null;

    flags = d3d, visible;               // para que el panel sea visible        (9)
}

//---------------------------------------------------------------
// (8) Funciones para los botones
//---------------------------------------------------------------
function girar_izquierda()
{
    if (gn_inc_rot < 0)
    {
        gn_inc_rot = 1;
    }
    else
    {
        gn_inc_rot += 1;        
    }
}

function girar_derecha()
{
    if (gn_inc_rot > 0)
    {
        gn_inc_rot = -1;        
    }
    else
    {
        gn_inc_rot -= 1;
    }
}

 

Y aqui pudo haber acabado el tutorial, pero hay algo que no me gusta del código anterior. Ahora repitan despues de mi: EL DISEÑO DE PROGRAMAS QUE HACEN USO DE VARIABLES GLOBALES PARA CONTROLAR SU FLUJO NO ES SEGURO. ¿Cuantas veces no te has repetido eso en tu clase de diseño de software?. Y aunque en los programas de la vida real siempre existe necesidad de una u otra variable global para controlar el comportamiento general del programa, el arte de programar radica en minimizar el numero de dichas variables. Sólo imagina el programa anterior creciendo en numero de entidades, con cientos de acciones, cientos de funciones y miles de variables globales siendo modificadas quién sabe donde, alterando el comportamiento de quién sabe qué... (Pesadilla).

 

Felizmente hay otro modo de programar, y es inclusive mas lógico, pues es lo que encuentras en la vida real. Solo imagina por un momento que eres la entidad numero dos mil millones y algo del juego de la vida "Surviving II" (que por supuesto corre en Linux y no necesita DirectAlgo). Tu, como entidad puedes realizar acciones: caminar, pensar, saltar, etc. Sin embargo todas tus acciones estan controladas por propiedades inherentes a ti: tu tienes una velocidad maxima de caminata de 3 Kmph, un IQ de 125, un peso de 74 kg, etc, etc... Todas estas propiedades te pertenecen a ti y definen lo que puedes hacer. El que programó este juego no creó miles de variables globales para controlar cada entidad, fue mucho mas inteligente: creo las entidades y estableció cúales eran las propiedades inherentes de cada una (en otras palabras definió cuales eran sus SKILLS). Las acciones que controlan el comportamiento de estas entidades hacen uso de esas propiedades para controlar su flujo. Sin embargo date cuenta que aun son necesarios ciertos parametros globales, como la gravedad por ejemplo, porque aunque tu y la entidad de tu vecino pueden realizar la accion de saltar y puedan tener diferente peso, la aceleración de caída siempre debe ser la misma, independientemente de la entidad. Ahora deja de imaginar que estás en ese juego y no pruebes si es cierto eso de la gravedad tirándote del segundo piso. Quizás en este juego solo tengas una vida... ups. Como este era un tutorial introductorio no pensaba abrumarte con PUNTEROS A ENTIDADES ni SKILLS, pero ya ves no estás con suerte.

 

Un puntero a entidad es una manera de referirte a una entidad para poder controlarla, en C-script se declara asi:

entity* ge_cubo;

Los SKILLS en C-script son simplemente posiciones de memoria donde puedes guardar valores, tienes 100 posiciones libres por entidad, asi que te recomiendo usarlas sin escatimar. La manera de acceder a determinado skill de un objeto es la siguiente:

ge_cubo.skill50

En el ejemplo estamos accediendo al skill numero 50 de la entidad a la que hace referencia ge_cubo, pero como los skill representan propiedades de un objeto es bueno darles un nombre descriptivo, para eso empleamos DEFINICIONES DE SKILLS asi:

define _vel_rot, skill50;

Ahora podemos acceder al skill 50 de la entidad asi:

ge_cubo._vel_rot

En este caso queremos que el skill 50 almacene la velocidad de rotacion. El script original HOLA.WDL hace uso de esta técnica y elimina la variable global gn_inc_rot. Te reto a investigar que secretos mas oculta este script. :-P.

 

 


//======================================================================================
// DEMO MODIFICADO PARA USAR SKILLS (Y OTROS SECRETOS)
//--------------------------------------------------------------------------------------
define  _vel_rot,   skill50;            // declaracion del skill50 para
                                        // almacenar la velocidad de rotacion.

//======================================================================================
// VARIABLES GLOBALES
//--------------------------------------------------------------------------------------
string  gs_nombre_nivel = <hola.wmb>;   // nombre del nivel a cargar
                                        
entity* ge_cubo;                        // puntero al cubo para poderlo controlar
//______________________________________________________________________________________

//======================================================================================
// PANELES
//--------------------------------------------------------------------------------------
bmap bmp_mouse          = <arrow.bmp>;
bmap bmp_derecha        = <fd.bmp>;
bmap bmp_izquierda      = <fi.bmp>;
bmap bmp_derecha_on     = <fd_on.bmp>;
bmap bmp_izquierda_on   = <fi_on.bmp>;

panel panel_control
{
    button = 100, 350, bmp_izquierda, bmp_izquierda, bmp_izquierda_on, girar_izquierda, null, null;
    button = 500, 350, bmp_derecha, bmp_derecha, bmp_derecha_on, girar_derecha, null, null;
    flags = d3d, visible;
}
//______________________________________________________________________________________

//======================================================================================
// ACCIONES
//--------------------------------------------------------------------------------------
action rotar_cubo
{
    if (my != null)      // es importante verificar que el puntero no sea NULL
    {
        ge_cubo         = my;   // guardamos el puntero a entidad de este cubo

        my._vel_rot     = 1;    // establecemos la velocidad de rotacion
        
        my.red          = 255;  // propiedades para que el cubo brille
        my.green        = 255;
        my.blue         = 255;              
        my.lightrange   = 0;
        my.bright       = on;

        
        while (1)               // comenzar a rotar el cubo
        {
            my.pan = my.pan + my._vel_rot;
            wait(1);
        }
    }
}
//______________________________________________________________________________________

//======================================================================================
// FUNCIONES GLOBALES
//--------------------------------------------------------------------------------------
function encender() // <- SECRETO AQUI!!! (presiona la tecla [ESPACIO])
{
    proc_kill (4);  // esta instruccion elimina otras instancias de esta funcion

    if (ge_cubo != null)
    {
        ge_cubo.lightrange = 150;
        ge_cubo.transparent = on;
        while (ge_cubo.lightrange > 50)
        {
            ge_cubo.lightrange -= 1.25;
            wait(1);
        }
        sleep(0.5);
        ge_cubo.lightrange = 0;
        ge_cubo.transparent = off;      
    }
}

function girar_izquierda()
{
    if (ge_cubo != null)
    {
        my = ge_cubo;
        
        if (my._vel_rot < 0)    {my._vel_rot = 1;}
        else                    {my._vel_rot += 1;}        
    }
}

function girar_derecha()
{
    if (ge_cubo != null)
    {
        my = ge_cubo;
        
        if (my._vel_rot > 0)    {my._vel_rot = -1;}
        else                    {my._vel_rot -= 1;}        
    }
}

function mouse_move()
{
    mouse_mode = 2;
    mouse_map = bmp_mouse;
    
    while(1)
    {
        mouse_pos.x = pointer.x;
        mouse_pos.y = pointer.y;
        wait(1);
    }
}

function camera_move()
{
    while(1)
    {
        if (key_home)       {camera.tilt += 1;}
        
        if (key_end)        {camera.tilt -= 1;}
        
        if (key_pgup)       {camera.z += 1;}
        
        if (key_pgdn)       {camera.z -= 1;}

        if (key_cuu)        {camera.x += 1;}
        
        if (key_cud)        {camera.x -= 1;}
        
        if (key_cul)        {camera.y += 1;}
        
        if (key_cur)        {camera.y -= 1;}
        
        wait(1);
    }
}
//______________________________________________________________________________________

// resolucion de pantalla
var video_mode = 6; // 640x480

//======================================================================================
// MAIN (Funcion principal de inicio)
//--------------------------------------------------------------------------------------
function main()
{
    warn_level = 2;         // para indicar malas texturas y codigo wdl

    wait(3);                // esperar 3 frames (para el triple buffering) 

    level_load(gs_nombre_nivel);    // cargar el nivel...

    wait(2);                // ESPERAR que el nivel se cargue
    
    mouse_move();           // iniciar funcion para sensar el mouse
    
    camera_move();          // iniciar funcion para sensar teclado
}
//______________________________________________________________________________________

//======================================================================================
// TECLAS
//--------------------------------------------------------------------------------------
on_space = encender;
//______________________________________________________________________________________

Aquí termina este simple tutorial que espero te ayude a programar en C-script. Cuando yo comencé no encontré nada en español, sólo páginas en ingles o alemán y muy pocas con una explicación simple de como hechar a andar tu proyecto. Espero que ahora nadie se excuse en el idioma para poder aprender. Los tiempos han cambiado, el motor gráfico ha cambiado un montón y seguirá cambiando sin duda, los lenguajes de programación se hacen caducos y mueren, pero los principios generales se mantienen, los conceptos de NIVELES, ENTIDADES, ACCIONES Y SKILLS que hemos visto son comunes para todos los motores gráficos así que fácilmente podrás trasladar todo este conocimiento a otros entornos de desarrollo. Te deseo suerte.

 

 

<ex>

 

 


Diseño y coordinación : Esaú Rodriguez Oscanoa - Lima - Per�

Esta página se ve mejor en Mozilla Firefox a 1024x768


Hosted by www.Geocities.ws

1