| |
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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]:
|
|
Te saldrá un cuadro con
información del objeto, selecciona la pestaña [behaviour],
deberías ver esto:
|
|
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>
|
| |
|