|
Programacion
Bueno,
como yo no conozco mucho del tema, el texto que explica un poco sobre
la programacion del Quake-C lo he exrtaido del numero 49 de la revista
PCMANIA.
Lamentablemente no tengo el numero siguiente donde quien escribe iba
a comenzar a crear un personaje nuevo, pero voy a tratar de conseguir
el articulo con la continuacion.
Para poder
hacer nuestras pruebas necesitaremos el codigo
fuente (ver. 1.06) con los comportamientos de todo lo que hay
en QUAKE y el compilador QCC (115 Kb) o tambien PROQCC
(87 Kb)
Quake-C,
creado por John Carmack, no pasara a la historia
como un lenguaje de multiples aplicaciones ni por ser especialmente
eficaz (como reconoce el propio autor), ni por presentar caracteristicas
nuevas con respecto al al lenguaje del cual desciende. El proposito
de Quake-C es exclusivamente ludico.
Con Quake-C la posibilidad de modificar o crear comportamientos
"inteligentes" para personajes virtuales esta al alcance
de cualquier programador. Si a ello, añadimos el hecho de que dichos
personajes pertenecen al entorno virtual para PC
mas atractivo del momento, comprenderemos un poco mas la pequeña revolucion
que supone "Quake".
Por esta razon, estudiaremos este tema con el deseo de aprender lo
suficiente para llegar a diseñar personajes propios para este juego.
No debeis esperar excesivas precisiones en el contenido de este articulo.
Para empezar no hay demasiada informacion sobre Quake-C
o sobre el Qcc (Quake-C Compiler). Carmack adjunta
algunos datos en el fichero "readme" que
acompaña al compilador y en el archivo qcc.h que
forma parte de los fuentes del compilador, los cuales, al igual que
los fuentes con los comportamientos del juego, pueden ser hallados
en Internet. Otra funte de informacion son los diversos faqs que,
en numero aun escaso, existen sobre este particular.
Quake-C presenta muchas similitudes
con C pero las semejanzas se presentan mas a nivel
sintactico que en lo relativo a filosofia de programacion. Asi, Quake-C
emplea los mismos caracteres para escribir comentarios, y usa los
mismos operadores y sentencias if
y while
pero, en
cambio, presenta una serie de diferencias y limitaciones con respecto
al C estandar. No se puede, por ejemplo, definir
nuevos tipos partiendo de otros ya existentes, ni asignar un nuevo
nombre a un tipo, ni definir nuevas estructuras. Todas las variables
son globales por defecto y pueden ser accedidas desde cualquier funcion.
(Pero si anteponemos la palabra local, la variable solo sera accesible
desde la funcion en la que se haya declarado). El lenguaje esta fuertemente
tipado y no se usan casts.
En cuanto a las funciones, tambien hay algunas diferencias con respecto
a las del C estandar. Comencemos con los prototipos.
Estos, en Quake-C, son del modo:
tipo
(tipo param1, tipo param2,...) nombre_funcion;
...y
para la estuctura general de la funcion, tenemos que...
tipo (tipo param1, tipo param2,..) nombre_funcion=
{
...
codigo
...
};
En qcc.h,
Carmack ofrece el siguiente ejemplo...
void()
MyFunction;
// El prototipo
void() MyFunction=
//sigue el cuerpo de la funcion
{
dprint ("we're here\n");
};
Las funciones de Quake-C no
pueden tener mas de 8 parametros. Por otro lado, en este lenguaje
existen dos tipos adicionales de funciones; las funciones frame y
las llamadas funciones built.
Las primeras
han sido diseñadas por conveniencia para facilitar la creacion de
secuencias de annimacion, y las siguientes se emplean para cambiar
valores de datos que no debemos intentar modificar directamente bajo
pena de cuelgue.
Naturalmente, Quake-C tiene sus limitaciones. El
dialecto no puede ser usado, por ejemplo, para permitirnos cambiar
el potente motor 3D de "Quake".
Realmente en el juego podemos distinguir entre dos tipos de codigo;
el que forma parte Quake.exe, que no es modificable
y que contiene el motor 3D y las funciones de bajo
nivel, y el que forma parte de progs.dat, el cual
es el resultado de la compilacion de los fuentes con la IA
modificada de los personajes y se apoya en Quake.exe.
ENTIDADES
Los tipos simples de datos permitidos por Quake-C
son void,
float, string, vector
y
entity.
El tipo
vector se emplea para especificar posiciones o direcciones en el espacio
virtual de "Quake". Emplea tres valores
en flotante que deben separarse con espacios y englobarse como en
los siguientes ejemplos:
'0 0 0'
'20.5 -10 0.00001'
Las entidades
son estructuras de datos. Los monstruos, los jugadores y los niveles
son cosiderados como entidades cuyo estado podemos, a veces, alterar.
Hay tres tipos de entidades; las estaticas, las temporales y las dinamicas.
Las primeras se emplean para referenciar a objetos, tales como luces,
que no interaccionan con el resto de los elementos del juego. El maximo
numero permitido de entidades estaticas es de 127 y podemos crearlas
usando la funcion markestatic().
Sin embargo,
una vez creadas, las entidades estaticas no pueden ser borradas. Las
entidades temporales, por otro lado, son cualquier cosa que tenga
un limitado tiempo de vida en el universo virtual de "Quake".
Son entidades temporales las balas de rifle, los clavos del lanzador,
los rayos del lanzarayos, etc. Estas entidades no nesecitan ser borradas
por el programador y desaparecen por si mismas.
Por ultimo, las entidades dinamicas son aquellos elementos que exhiben
cambios en su comportamiento o apariencia, al interaccionar con otras
entidades del juego. Podemos estudiar los campos que forman una estructura
dinamica en el fichero defs.qc, a partir del texto "SOURCE
FOR ENTVARS_TC STRUCTURE".
Y podemos añadir nuestros propios campos al final de esta estructura,
despues de end_sys_fields
(para,
por ejemplo, guardar un nuevo tipo de dato para un arma nueva o lo
que sea). Los campos de esta estructura son compartidos por Quake.exe
y Quake.c y pueden leerse libremente pero no deben
ser escritos directamente. Notese que Quake-C no
proteje la escritura directa en estos campos a pesar de que dicha
operacion puede condusir al desastre. (Esta escritura solo debe realizarse
empleando funciones built).
Veamos ahora algunos de los campos que pueden modificarse:
CAMPOS DE ENTIDADES DINAMICAS
El nombre del fichero que contiene el modelo de la entidad se especifica
en: string
model;
Para apuntar
al frame adecuado dentro del indice de modelos se usa la variable
frame. Aqui la palabra frame no alude a un bitmap sino a un modelo
3D que corresponde a una de las posturas de alguna de las
secuencias animadas del modelo (quiza model-frame
sea una palabra mas adecuada para designar a una de estas posturas).
Los frames deben ser definidos por un contructor
&
frame en
el fichero de modelos y pueden ser referenciados por el codigo como
$xxx (siendo xxx el nombre del frame).
Por otro lado puede ocurrir que se disponga de varias texturas para
recubrir al modelo. Si este es el caso, entonces el valor que sigue
a la variable skin
se empleara
como indice a la lista de skins (pieles). El valor
debera estar comprendido en el rango correcto.
Otra variable que afecta a la apariencia de los personajes es effects.
Esta variable indica el efecto luminoso a que esta sujeta la entidad.
Effects
puede ser
empleada para crear entidades luminosas (como en el mod Cujo)
o campos de puntos de luz.
En cuanto a la posicion y orientacion de los personajes, la estructura
dispone de las variables
origin,
mins, maxs, size, absmin, absmax, oldorigin
y
angles.
Origin tiene la posicion del model-frame y podemos conocer las coordenadas
de cada eje usando origin_x,
origin_y y
origin_z.
Mins
y
max
controlan
la extencion, relativa al origen, de la caja
bounding
del personaje.
Estas "bounding"
son cajas invisibles que recubren a los personajes y se emplean para
efectuar las detecciones (con paredes, disparos, etc.). Esto implica
que las deteciones no son excesivamente precisas pero si muy rapidas.
Como antes, para conocer los valores en cada eje, usaremos los sufijos
_x,
_y y
_z
(o sea,
por ejemplo, mins_x,
mons_y y
mins_z).
"Oldorigin"
guarda la posicion anterior y "angles"
la orientacion del personaje.
Ademas, podemos especificar detalles adicionales tales como si la
entidad puede bloquearnos el paso o no (solid),
el tipo de movimiento (movetype),
la velocidad (velocity),
la velocidad de rotacion (yaw_speed)
y muchas mas que veremos en detalle mas adelante.
FUNCIONES BUILT
Entre las funciones built
hay funciones
matematicas, de trabajo con vectores, de uso de sonidos, de manejo
y movimiento de entidades, el chequeo de colisiones y control de luchas
y disparos, de depuracion del programa y de otro tipo. El conocimiento
de la existencia de estas funciones es el punto mas basico en la programacion
de ficheros mod. Entre las funciones matematicas existen floor(),
cel() y
fabs(),
que funcionan como sus equivalentes en C estandar,
y random(),
que devuelve un numero aleatorio que oscila entre 0 y 1.
Entre las funciones para trabajo con vectores tenemos por ejemplo:
float
vlen(vector v)
...que
retorna a la longitud del vector v (nunca inferior a 0). Podemos encontrar
un ejemplo de vlen()
en la funcion
CUJO_bite()
(mordisco
de perro cujo) en el fichero cujoai.qc del mod
cujo10. Alli se define unn vector llamado delta
que se
hace igual a la diferencia entre la posicion del enemigo y la de nuestro
fiel chucho:
delta = self.enemy.origin -
self.origin;
Despues, en la siguiente linea, se comprueba si la distancia es superior
a 100 unidades y, de ser asi, se abandona la funcion:
if(vlen(delta) > 100) return;
De lo contrario la distancia entre ambos luchadores esta dentro del
rango previsto y Cujo procede a morder a su contrincante causandole
un daño que dependera del valor aleatorio devuelto por la funcion
random().
Otra funcion util es;
float vectoryaw(vector v)
La cual calcula el angulo correspondiente entre posicion y orientacion
del personaje y el vector v. (Se entiende que este
angulo es el usado en el plano del suelo sobre el que se halla la
entidad).
SOUND()
La funcion sound tiene el prototipo:
void
sound (entity souse, float channel, string sample, float volume, float
attenuation)
Entity
es la entidad
que emite el sonido, channel
es el canal
empleado para la emision,
sample
es el nombre
del fichero wav que contiene el sonido (ejemplo: "dog/idle.wav")
, volume
es el control
de volumen -que puede osilar de 0 (nada) a 1 (maximo volumen) y
attenuation
es un factor de atenuacion del sonido (podemos encontrar valores para
attenuation y otros paramentros en el fichero deft.qc).
El sonido no es esencial para el funcionamiento de la entidad pero
contribuye a darle mas realismo. Algunos programadores de mods no
se molestan en poner sonidos a sus creaciones pero Cujo
y Wisp si los tienen.
FUNCIONES BUILT DE MANEJO Y MOVIMIENTO
DE ENTIDADES
Con spawn()
podemos crear una nueva entidad vacia. Sus campos podran ser llenados
o manualmente, o bien llamando alguna de las funciones setup de entidades.
La entidad asi creada podra ser eliminada mas adelante con
remove(num_entidad)
Otras funciones
nos permitiran manipular diversos modos a las entidades, por ejemplo
findradius(),
cuyo prototipo es: entity
findradius (vector origin, float radius)
Retorna
una cadena de entidades cuyos origenes estan dentro de unn area esferica
cuyo centro y radio son
origin
y
radius
respectivamente.
La entidad devuelta es el primer elemento de la cadena y findradius()
se utiliza normalmente pare decir cuales son las entidades victimas
de una explosion y proceder a su tratamiento. El siguiente ejemplo
ha sido tomado de qc_built.htm:
e = findradius( origin, radius)
while(e)
{
T_Damage(e, ...) // Let God sort his ones!
e = e.chain
)
En caso
de que no haya ninguna victima el valor devuelto por la funcion es
"false",
con lo que no se llega a entrar en el bucle de tratamiento. Para el
movimiento de las entidades podemos usar las funciones walkmove(),
setorigin()
o movetogoal().
La primera retorna "false"
si el movimiento no es posible, lo cual se emplea para detectar monstruos.
Setorigin()
cambia la posicion de una entidad. Sus parametros son, en este orden,
el numero de la entidad y el vector con la nueva posicion. Esta funcion
es empleada para teletransporte y es el unico metodo alternativo al
movimiento fisico. (Cambiar directamente el valor de posicion origin,
no es recomendable). Por ultimo,
movetogoal()
se emplea para recorrer una distancia especificada, en el sentido
del movimiento.
Otra funcion de uso frecuente, aunque esta vez para trazar lineas
de tiro, es trace
line()
cuyo prototipo es;
traceline (vector v1, vector v2, float nomonsters, entity fornet)
Los dos
primeros parametros son el principio y el final de la linea a trazar.
El siguiente parametro "nomonster",
debera ponerse TRUE
si la linea
puede atravesar monstruos y a
FALSE
si no va
a ser asi. La linea trazada puede ser bloqueada por cajas bounding
(personajes) y entidades bsp (arquitectura del nivel).
Finalmente, el parametro forent
indica la entidad que debe ignorarse en el trazado (la que dispara,
claro). La funcion devuelve algunos valores en variables globales.
Por ejemplo, en trace_fraction
se devolvera un porsentaje que indica la fraccionde linea que puede
recorrerse sin hallar obstaculos, siendo su valor igual a 1 si no
se encuentra ninguno.
Para crear efectos de particulas Quake-C nos ofrece
la funcion particle()
cuyo prototipo es;
void particle(vector origin, vector dir, float color, float count)
Origin
es la posicion
inicial , dir
la direccion
inicial, color
el indice de colores y
count
el tiempo de vida en segundos. El valor de color puede ser de 0 (para
trozos), 75 para amarillo, 73 para sangre y 225 para representar a
la entidad dañada.
Jose
Manuel Muñoz
|