Curso Fenix Programcion Videojuegos
Curso Fenix Programcion Videojuegos
DE INICIACIÓN
A LA PROGRAMACIÓN
DE VIDEOJUEGOS
CON EL LENGUAJE
FENIX v0.84b
(en Windows)
¿Qué es "programar"?
1
su mayoría) miniaturizados y encapsulados en una pastilla
llamada microchip. Hay de muchos tipos y su estudio es muy
interesante, pero no entraremos en más detalles. Y luego está
el programa. No se trata de algo físico: está compuesto de
órdenes que van a guiar al procesador en sus tareas de
manipulación de la información. Es decir, un programa no es
más que un conjunto de comandos que un procesador ejecuta
siguiendo un orden determinado para lograr alcanzar un
objetivo propuesto. Como programadores que somos,
escribiremos esas órdenes en un archivo, como si
escribiéramos un documento de texto, y luego se lo daremos
como alimento al procesador. En el momento de la verdad,
cuando queramos "poner en marcha" el programa, esas
instrucciones que escribimos una vez van a llegar al
procesador en forma de impulsos eléctricos, y el conjunto de
señales que interprete nuestro procesador dará como resultado
la ejecución de nuestro programa.
1001011101010101100110010110101010101010101101010100110101010
11010
2
equivocarse), el código máquina válido para un procesador no
lo es para otro, debido a su propia construcción electrónica
interna.Por lo tanto, un mismo programa se tenía que
codificar en tantos códigos máquina como en procesadores se
quisiera que funcionara.
.MODEL SMALL
.CODE
Programa:
MOV AX,@DATA
MOV DS,AX
MOV DX,Offset Palabra
MOV AH,9
INT 21h
MOV AX,4C00h
INT 21h
.DATA
Palabra DB 'Hola$'
.STACK
END Programa
3
seguidamente con “n ruta_del_fichero”.Para salir del programa
“debug” es pulsando “q”.
#include <stdio.h>
void main (void)
{
printf("Hola");
}
4
Existe una gran variedad de lenguajes de programación.
La mayoría es de propósito general, esto es, sirven para dar
solución a problemas de ámbitos muy dispares. Lo mismo se
usan para crear (o más técnicamente, desarrollar)
aplicaciones científicas que para videojuegos. Otros
lenguajes son de propósito específico -como Fénix- y están
muy bien optimizados para la resolución de problemas de un
campo muy concreto del saber. Para el primer caso tenemos el
lenguaje C (aunque su aprovechamiento máximo se produce en la
programación de sistemas a bajo nivel), C++ (una evolución de
C), C#, Delphi, Java, Basic, etc. En el segundo caso tenemos
lenguajes orientados al tratamiento de las base de datos
empresariales como Cobol, Clipper, SQL,etc,o lenguajes
orientados a la resolución de problemas matemáticos como
Fortran, MatLab,etc, lenguajes orientados a la generación de
páginas web como HTML, Javascript, Php,ASP, etc,lenguajes
orientados a la creación de videojuegos como Blitz3D o el
propio Fénix, etc,etc. Todos ellos forman parte de los
denominados lenguajes de alto nivel pues su sintaxis es muy
próxima al lenguaje humano. Sin embargo, sigue siendo
imprescindible traducir estos lenguajes al lenguaje que
entiende el procesador, el código máquina.
Editores,intérpretes y compiladores
5
decanta por el uso de un lenguaje de programación
interpretado o compilado.
6
procesador de una familia determinada -hemos comentado antes
que los códigos máquina son muy poco transportables- , por
ejemplo para los Pentium, y no podrá ejecutarse en otra
plataforma, como un Macintosh. Y no sólo para las plataformas
hardwre, sino también para los sistemas operativos. Un
programa compilado para Linux no funcionará bajo Windows.
Esto obliga a los programadores a recompilar el programa para
las diferentes plataformas y entornos en los que va a
trabajar, generando diferentes versiones del mismo programa.
Además el programador también se encuentra con el trastorno
de tener que recompilar el programa cada vez que le hace una
modificación o corrección al código fuente.
Las librerías
7
a su vez, podían ser utilizados como un comando más "no
estándar" del lenguaje utilizado. Es decir, que existía la
posibilidad de generar nuevos comandos para un lenguaje
determinado, a partir de los comandos iniciales de ese mismo
lenguaje, y hacer disponibles esos nuevos comandos para los
demás programadores. La utilidad era obvia: ¿para qué
escribir un programa de 100 líneas que me dibujara un círculo
en la pantalla cada vez que quería dibujar el círculo ? Lo
más inteligente era escribir ese programa una vez, y
convertirlo en un comando más del lenguaje, para que así todo
el mundo a partir de entonces no tuviera que empezar de 0 y
escribir siempre esas 100 líneas, sino que solamente tuviera
que escribir ese nuevo comando que él solito dibujaba un
círculo.Así pues, cada programador se podía crear sus propios
comandos personales que le facilitaban la faena: los
codificaba una vez (es decir, escribía lo que quería que
hiciera ese comando, a partir de comandos más elementales) y
los podía utilizar a partir de entonces cuando quisiera. Era
una forma perfecta para encapsular la dificultad de un
programa y acelerar el desarrollo. Evidentemente, estos
nuevos comandos - a partir de ahora los llamaremos funciones-
se compartían entre los programadores, y a los que no habían
diseñado esa función "no estándar", no les interesaba mucho
cómo estaba hecho por dentro esa función, lo que les
importaba es que hiciera bien lo que tenía que hacer.
8
cero, hay librerías de red, que posibilitan tener funciones
de comunicación entre ordenadores,etc. Hay que saber también
que un nombre técnico para llamar a las librerías en general
es el de API (Application Programming Interface -Interfaz de
programación de aplicaciones), así que en nuestro programa
podríamos incorporar una API de entrada/salida, una API GUI,
una API de red,..., y hacer uso de las funciones incluídas en
ellas.
9
bien en 2D o bien en 3D; y por multimedia que además dé
soporte al control y gestión de sonido,
música,vídeo,interacción teclado/ratón/joystick/...,etc.
10
incursión en el mundo gráfico. No obstante, no es muy
usada en juegos profesionales (o al menos no muchos
aceptan que lo usan), quizás porque está diseñada con
una visión general de cómo funciona un videojuego -por
tanto, es ideal para novatos y como hobby- , pero en un
juego profesional a los programadores les gusta exprimir
hasta las raíces de lo que están utilizando; por ejemplo,
Allegro no tiene soporte para aceleración gráfica.
DirectX
( http://msdn.microsoft.com/directx/ ) :DirectX es la
librería de Microsoft por lo que únicamente puedes hacer
Juegos para Windows con ella. Esa no es la única
desventaja, también es difícil de programar.Para que
quede más claro, no es recomendable comenzar por DirectX
a menos que te decidas a hacer Juegos para Windows
11
exclusivamente y tengas un sólido respaldo de
conocimientos sobre programación general. Recomiendo
dejar la dificultad de DirectX para una segunda etapa,
cuando ya sepas manejar gráficos y demás elementos
multimedia. Tarde o temprano tendrás que aprender a
trabajar con ella si quieres hacer videojuegos
ambiciosos, pero es mejor si te introduces en el mundo
de las DirectX después de haber programado ya en otras
librerías. DirectX fue diseñada por Microsoft con el fin
de permitir a los programadores escribir programas
multimedia para sistemas Windows de manera fácil, sin
tener que preocuparse sobre qué hardware está corriendo
y como funciona, como es el caso del tipo de tarjeta de
video, etc, es decir, sin tener que escribir código
específico de hardware. Para la mayoría de las
aplicaciones tradicionales, el acceso a funciones de
hardware se hacen a través del sistema operativo; sin
embargo, para las aplicaciones multimedia, que son más
exigentes, esta vía puede resultar bastante
lenta;DirectX es un conjunto de librerías que
permitieran acceso a funciones de hardware de forma más
directa, sin pasar por todos los callejones que
usualmente crea el sistema operativo. Obviamente esto
hace que , además de ser un producto de Microsoft,
DirectX está íntimamente ligado con el sistema operativo
Windows. Por eso, esto hace que todas aquellas
aplicaciones sean, en extremo, difíciles de portar a
otras plataformas,es decir, una vez que hayas terminado
de programar tu juego estará condenado a trabajar
solamente en Windows, y cuando quieras convertir tu
juego para que pueda correr en otras computadoras que
usen Mac o Linux tendrás que hacerle muchos cambio al
código, lo cual no es nada deseable a menos que sepas
que el único mercado al que va dirigido tu juego son
personas con una computadora con Windows. No obstante,
una de las principales ventajas que tiene es que no está
hecho solamente para la parte gráfica, sino que con
DirectX puedes manejar toda la multimedia de la
computadora como sonidos, música, dispositivos de
Entrada/Salida como teclado, joystick, y varias otras
cosas más, por lo que en este sentido es ideal para
programa videojuegos. Realmente DirectX esta compuesto
de 7 compone ntes:
12
-Direct3D: es un motor de rendering para
gráficos 3D en tiempo real que integra una API de
bajo nivel para el render de polígonos y vértices y
uno de alto nivel para la manipulaciión de escenas
complejas en 3D.
-DirectShow: es una arquitectura multimedia
que divide el procesamiento de tareas multimedia,
como la reproducción de video, en un conjunto de
pasos conocidos como filtros: éstos tienen un
número de entradas y salidas que se conectan entre
ellas, ofreciendo gran flexibilidad y modularidad
al desarrollar, que puede combinar los filtros
según sus necesidades.
-DirectInput: permite recoger información en
tiempo real de l ratón, teclado y joysticks
-DirectSound: ofrece drivers y mezcladores de
sonido con soporte Dolby, los cuales posibilitan un
rendimiento óptimo de sonido posicional en 3D,
permitiendo a los programadores de juegos situar
eventos de sonido en cualquier punto del espacio
perceptual del usuario.
-DirectMusic: es mucho más que simplemente
reproducir sonidos; provee un sistema completo para
implementar bandas sonoras dinámicas y
sincronizadas que aprovechan la acelaración
hardware y efectos de posicionamientos 3D avanzado,
entre muchas otras ventajas.
-DirectPlay: proporciona protocolos
independientes para funciones de red, para
videojuegos de varios jugadores en Internet.
13
-OpenGL -Open Graphics Library-
( http://www.opengl.org/ ) : OpenGL es una librería 3D
profesional multiplataforma, y esa es su mayor ventaja.
En la actualidad muchas personas utilizan Linux u otros
sistemas ajenos a Microsoft lo que abre nuevos e
importantes nichos de mercado. Aprender a programar en
OpenGL nos facilita el ingreso a estos mercados y amplía
nuestros horizontes. Es interesante conocerla e
implementarla con SDL para hacer tus primeros trabajos
serios usando C. Es la competencia de Direct3D, ya que,
a diferencia de DirectX (en conjunto), esta API sólo
posee rutinas para el manejo de gráficos y no tiene
soporte para sonido, entrada, red, etc, el cual se ha de
buscar en otras librerías (SDL, Allegro o más
específicas).La especificación del API de OpenGL es
controlada por un grupo llamado Architecture Review
Board, conformado por varias empresas importantes del
sector de las tarjetas gráficas como nVidia, ATI,
Creative Labs, SGI, entre otras. La implementación libre
por excelencia de OpenGL se llama MESA
(http://www.mesa3d.org ).OpenGL está pensado para
funcionar en sistemas con aceleración hardware, por lo
que los fabricantes de tarjetas han de incluir soporte
OpenGL en sus productos.
#include <windows.h>
#include <conio.h>
#include <gl\gl.h>
#include <gl\glaux.h>
glClearColor(0.0f,0.0f,1.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
14
getch();
}
15
(http://openil.sourceforge.net ) o Cg, intentando ofrecer
una competencia global a DirectX.
16
incluso si ningún conocimiento de programación, siguiendo sus
tutoriales paso a paso, un juego simple puede ser construido
en una tarde, y usando el C -Script juegos de calidad
comercial pueden ser creados y distribuidos con éxito. En la
página web hay demos de juegos, sección de preguntas
frecuentes (FAQs), un magazine de usuarios,etc.
17
Clanlib (http://www.clanlib.org): Librería libre y
multiplataforma para la
programación de
videojuegos 2D utilizando
C++.
18
Fenix de momento por sí solo no puede -, sin demasiado
esfuerzo. Y probablemente sea verdad (no los he probado),
pero tienen un pequeño inconveniente (llamémoslo así). Aparte
de que son productos comerciales y por tanto, para
adquirirlos hay que pagar un precio, no son libres. Y eso,
creo, que es un factor relativamente importante.
19
significa (entre otras cosas) que no tienes que pedir o pagar
permisos. También deberías tener la libertad de hacer
modificaciones y utilizarlas de manera privada en tu trabajo
u ocio, sin ni siquiera tener que anunciar que dichas
modificaciones existen. Si publicas tus cambios, no tienes
por qué avisar a nadie en particular, ni de ninguna manera en
particular. La libertad para usar un programa significa la
libertad para cualquier persona u organización de usarlo en
cualquier tipo de sistema informático, para cualquier clase
de trabajo, y sin tener obligación de comunicárselo al
desarrollador o a alguna otra entidad específica.
20
Supongo que conocerás, al menos de oido porque lo he
nombrado varias veces, a Linux, la estrella de los programas
libres. Linux es un sistema operativo competencia de Windows,
libre y gratuito. Es decir, que en vez de que tu ordenador
arranque en Windows –que se supone que lo has tenido que
pagar- , podría arrancar Linux gratuitamente, accediendo
igualmente a multitud de programas de todo tipo (ofimática,
multimedia, juegos,etc). Técnicamente, un sistema operativo
es un programa que se comunica directamente con el hardware
de la computadora (procesador, memoria, discos duros) y hace
de intermediario entre éste y los demás programas de todo
tipo que se puedan instalar en la máquina, ofreciendo a cada
programa los recursos hardware que necesita en cada momento.
Cuando decimos que un programa es portable o no , lo que
decimos es que puede ser utilizado con varios intermediaros
(con varios sistema operativos) o no. Linux es una
implementación escrita básicamente en C del sistema operativo
UNIX (uno más de entre los numerosos clónicos del histórico
Unix), que sale muy bien parado al compararlo con otros
sistema operativos comerciales. Comenzó como proyecto
personal del -entonces estudiante- Linus Torval ds, pero a
estas alturas el principal autor es la red Internet, desde
donde una gigantesca familia de programadores y usuarios
aportan diariamente su tiempo aumentando sus prestaciones y
dando información y soporte técnico mútuo. La versión
original -y aun predominante- comenzó para PCs compatibles
(Intel 386 y superiores), existiendo también en desarrollo
versiones para prácticamente todo tipo de plataformas
hardware:PowerPC, Sparc,Alpha, Mips, etc.
Debian (http://www.debian.org)
Suse (http://www.novell.com/linux/suse)
Fedora Core (http://fedora.redhat.com)
Mandriva (http://www.mandriva.com)
Gentoo (http://www.gentoo.org)
Ubuntu (http://www.ubuntulinux.org ó http://www.ubuntu-
es.org)
21
Si quieres ver más distribuciones, prueba en
http://www.linuxiso.org.
http://www.tutorialgames.net
Excelente portal en castellano dedicado a ofrecer ayudas
y documentación sobre distintos lenguajes de programación de
videojuegos, como Blit3D, Div o el propio Fénix
22
http://www.vjuegos.org
Comunidad Iberoamericana de Desarrolladores de
Videojuegos.
Portal en español enfocado a impulsar el desarrollo de
videojuegos a nivel profesional
http://www.gametutorials.com/
Otra excelente pagina para encontrar recursos.
http://www.gamasutra.com
El portal más importante sobre d esarrollo de videojuegos
a nivel profesional
http://www.gdmag.com
Revista para Desarrolladores de Videojuegos
http://www.gdconf.com
Congreso Internacional de Desarrolladores de Videojuegos
http://www.gamedev.net
Excelente pagina para desarrollar videojuegos con
cualquier librería.
http://www.igda.org/
International Game Developers Association
http://sp4br75.digiweb.psi.br/principal.htm
Buena pagina en español con algunos tutoriales sobre la
programacion de juegos en español.
http://www.a dva.com.ar
Asociación de Desarrolladores de Videojuegos Argentina
http://nehe.gamedev.net
Página con tutoriales sobre OpenGL y más.
http://www.webgamebuilder.com
Portal donde podrás descargar múltiples herramientas
(entre otras, el Blitz3D) para diseñar tus propios
juegos, enfocados preferentemente a la web, y consultar
los foros relacionados.
http://www.stratos-ad.com
Punto de encuentro para desarrolladores de videojuegos
hispanos
23
Conceptos básicos de multimedia para el principiante
Gráficos
Profundidad de color:
24
sistema transmite a la tarjeta información sobre los píxeles
individuales que tienen que prepresentarse en la pantalla.La
tarjeta de video es en realidad una computadora ella sola:
tiene su propio procesador y su propia memoria, pudiendo
realizar la mayor parte de sus funciones de forma
independiente del procesador principal del sistema.Las
capacidades de la tarjeta de vídeo pueden tener un efecto
importante en el rendimiento global de un PC; puede suceder
que un equipo con el procesador más rápido y con una gran
cantidad de memoria funcione con lentitud con una tarjeta de
video por debajo del estándar: esto es especialmente cierto
con las aplicaaciones intensivas de video, especialmente con
los juegos.
25
Resolución:
Peso:
26
original: tendrás que prescindir de algunos datos (compresión
con pérdida), o guardarla con menos colores de los que tiene
en realidad (bajar la profundidad de color), o hacer que se
componga de menos puntos de color que el original, y por
tanto, que sea más “granulosa” (bajar la resolución) o
hacerla más pequeña en tamaño (bajar los valores de las
dimensiones de altura y anchura).
Formato:
Gráficos vectoriales
*Ventajas:
27
alrededor de la pantalla, alargarlos, rotarlos,
duplicarlos o introducir distorsiones,etc.
-Algunos formatos permiten animación. Está ser
realiza de forma
sencilla mediante operaciones básicas como traslación o
rotación y no requiere un gran acopio de datos.
*Inconvenientes:
Mapa de bits
*Ventajas:
*Inconvenientes:
28
-En comparación, pesan más que las imágenes
vectoriaes, ya que los bitmaps contienen información
específica sobre cada píxel representado en la pantalla.
Formato GIF
29
distribuyendo en la paleta uniformemente unos colores
seleccionados, disponibles a lo largo del espacio de color
RGB –la cantidad de colores susceptibles de ser elegidos para
incluirse en una paleta depende de la profundidad de color
usada en ese momento- , o bien llenando la paleta de
gradaciones sutiles de un mismo color, y utilizar una u otra
paleta allí donde sea necesario. Cada pìxel tendrá asociado
pues en cada momento un valor numérico que es un índice
dentro de la paleta, el cual le dice al ordenador qué color
utilizar cuando se visualiza dicho píxel. Por ejemplo, en un
modo de video paletizado de 8 bits, cada píxel es un valor
que varía de 0 a 255. Por lo tanto, la paleta puede contener
256 colores y punto, que es lo que ocurre con las imágenes en
formato GIF, pero la gracia está en crear una paleta de 256
colores que representen los colores que más usaremos.
Formato BMP
30
Formato PNG
31
-Con este formato no pueden realizarse animaciones
aunque se ha desarrollado uno nuevo basado en este MNG
(Multiple Network Graphics) que si lo permite.
Video
32
-Edición: una vez realizada la digitalización, se
dispone de herramientas informáticas que facilitan la edición
(el tratamiento) de imagen y sonido y la generación de
efectos: ordenar, editar con corte para desprenderse de los
trozos no deseados, inclusión de transiciones, filtros,
transparencias, títulos, créditos…
33
16 millones de colores a 1.5Mbits/s (calidad VHS).
Posteriormente apareció el MPEG-2, pensado para la TV digital
(cable y satélite) y es el formato utilizado en el DVD. Las
pruebas actuales permiten distribuir secuencias de video de
720x485 píxeles. Es formato final, nunca para editar video.
Códecs:
34
ocupa mucho espacio, es el codificador que garantiza mayor
calidad. Por esta razón, es el más usado cuando se ha de
digitalizar material original que se ha de editar. El
problema que presenta este sistema es la falta de
compatibilidad entre las diferentes tarjetas digitalizadoras
(capturadoras) existentes con el codec M-JPEG, ya que cada
fabricante ha desarrollado su propia variante del formato.
35
conviene irse actualizando e incorporando los nuevos códec
que aparecen en el mercado según las necesidades.
3D
¿Qué es animar?
36
estroboscópico de su movimiento. En la actualidad, tanto el
cine como la televisión emiten y se graban a un mínimo de 24
imágenes por segundo ( o fps, frames por segundo).
37
Proceso de animación 3D
*GUIÓN y STORYBOARD:
*MODELAJE:
38
*APLICACIÓN DE TEXTURAS:
*ANIMACIÓN:
*ILUMINACIÓN:
39
*SOMBREADO:
*RENDERIZADO:
40
otros efectos de origen natural, como la interacción de la
luz con la atmósfera o el humo. Ejemplos de estas técnicas
incluyen los sistemas de partículas que pueden simular lluvia,
humo o fuego, wl muestreo volumétrico para simular niebla,
polvo y otros efectos atmosféricos, y las cáusticas para
simular el efecto de la luz al atravesar superficies
refractantes.
La aceleración hardware:
41
hardware es valiosa en situaciones que requieran renderizado
de objetos 3D tales como juegos, CAD 3D y modelamiento.
Sonido
42
intervalos regulares. La colección de los valores obtenidos
será ya una representación digital del sonido.
El proceso de digitalización:
43
Digitalización de alta y baja calidad. Codecs:
44
utiliza en muchos tipos de dispositivos portátiles, y es el
rey de los còdecs en el intercambio de música por internet.
Formatos de almacenamiento:
45
una digitalización a 44,1 KHz, 16 bits y estéreo, sin ningún
tipo de compresión. En un CD de 700 Mb caben unos 80 minutos
de audio.
Ficheros:
46
eléctrico otra vez, el cual a través de unos altavoces se
vuelve a convertir en sonido.
¿Qué es el MIDI?
47
fichero MIDI sea muy menor a la de un fichero de sonido
digital.
Aparatos MIDI
48
acordeones, baterías...) y de otras con diseños específicos
(sensores de luz, de sonido o de movimiento, mesitas
sensibles, platos giratorios...).
49
Cuando se utiliza una aplicación para reproducir un
archivo midi, la tarjeta recibe la señal igual que antes,
pero la información no se envía al mezclador directamente
porque el midi contiene instrucciones de programación, no
audio digitalizado. En su lugar, la información midi se envía
al sintetizador, que genera el audio digital a partir de las
instrucciones y pasa las señales de audio digital al
mezclador, repitiéndose el proceso de antes.
50
de sonido, suites 3D, etc son aplicaciones esenciales para
poder generar un juego creíble, atractivo, interesante y
profesional. Este curso sólo pretende iniciar al lector en el
conocimiento de un lenguaje de programación concreto como es
Fénix, dejando a un lado una explicación pormenerizada de
todas estas herramientas que también son imprescindibles. En
este sentido, el lector tendrá que hacer un trabajo de
búsqueda y estudio particular de aquellas aplicaciones que en
cada momento le convenga más o le ayuden mejor en su tarea de
dibujar, crear músicas, incluir animaciones, etc.
51
pues. No obstante, no es libre y es
de pago.
52
FreeHand (http://www.macromedia.com): Otro excelente editor
de gráficos vectoriales de la casa
Macromedia, más centrado en el mundo
editorial y de papel impreso.
Alternativas libres:
53
desarrolladores que tiraban para
adelante este proyecto. Esperemos
que se relance próximamente.
54
Editores de mapa de bits
55
optimizar esas imágenes e
incorporarles interactividad
avanzada.
Alternativas libres:
56
librería gráfica GTK+ en el sistema.
Una página web interesante de
soporte al usuario en español es
http://gimp.hispalinux.es
Editores de video
57
Alternativas libres:
Suites de animación 3D
58
empresa Pixar. Webs que pueden
ayudar al usuario de esta aplicación
pueden ser http://www.simplymaya.com
y http://www.mayatraining.com
Alternativas libres:
59
como soporte a la programación bajo
el lenguaje Python. Blender tiene
una particular interfaz de usuario,
que es implementada enteramente en
OpenGL y diseñada pensando en la
velocidad. Hoy en día todavía no
exprime todas las posibilidades que
los grandes productos ofrecen, pero
éste es un proyecto que está
gozando de una rápida evolución
debido a la gran participación de la
comunidad involucrada, así que es de
esperar mejoras importantes en
plazos cortos de tiempo.
60
Editores de sonido
61
(http://ardour.sourceforge.net), grabador multipista de midi
y audio al disco duro.
Alternativas libres:
62
archivo Wav y Ogg al disco
duro,entre otros.
63
converter.biz a ver si encuentras la aplicación que más se
ajuste a tus necesidades.
64
CAPÍTULO 1: PRIMER CONTACTO CON FÉNIX
65
librería gráfica SDL, la cual es portable a todos estos
sistemas operativos mencionados.
Historia de Fénix
66
aspectos de los proyectos: programación, edición gráfica y
sonora y un largo etc.
Fenix, inicialmente bajo el nombre DIVC y de naturaleza
GNU y gratuita, apareció de la mano de Jose Luis Cebrián como
una herramienta capaz de compilar y ejecutar esos juegos en
Linux. El nombre fue cambiado en la versión 0.6 del
compilador, que además introducía otras mejoras, como la
aparición de un fichero intermedio entre el entorno de
compilación y el entorno de ejecución. Ya no era necesario
distribuir el código fuente de un juego para poder jugar a
los juegos. La ventaja principal de esa práctica (similar en
concepto a Java) era clara, compilar en una plataforma y
ejecutar en muchas. En la versión 0.71 el proyecto quedó
parado, lo que dio lugar a múltiples versiones derivadas que
corregían fallos o añadían nuevas características.
La versión oficial de Fenix fue retomada por Slàinte en
el año 2002, viejo conocido de la comunidad DIV por ser el
webmaster de una de las páginas web más importantes para la
comunidad, quien continuó el proyecto bajo el nombre de Fenix
- Proyecto 1.0 al que pronto se reincorporaría su creador y
cuyo primer objetivo era limpiar el compilador de errores y
estabilizarlo. Desde entonces el compilador ha sufrido
numerosos cambios y mejoras, dejando de un lado la
compatibilidad con el lenguaje DIV, el desarrollo del cual
quedó paralizado hace tiempo en su versión 2 desde la quiebra
de l a empresa que lo comercializaba, Hammer Technologies. (De
hecho, DIV2 sólo es compatible con Ms -Dos y Windows 95/98,
pero no con Windows 2000 o XP). Actualmente, el foro de Fenix
(http://forum.divsite.net) es un lugar muy activo lleno de
gente que intercambia información, trucos y experiencias
sobre este lenguaje.
67
funciones, variables, características sintácticas y
entresijos del lenguaje. Sería útil como manual de
referencia, pero no enseñaría a programar ni contendría
tutoriales. Espero que con este curso se solucione en parte
este problema ;)
2)El copyright sobre DIV Games Studio lo posee
actualmente la compañía inglesa [FastTrak] quien mantiene un
proyecto para portar DIV a plataformas Windows modernas, en
las cuales la versión original de DIV no puede ejecutarse.
Las similitudes entre ambos proyectos ha llevado a varias
disputas que parecen solventadas con la tendencia del
proyecto a abandonar los elementos comunes con DIV Games
Studio en un pacto no escrito de mutua 'no agresión'.
3)El proyecto carece de una buena base multilingüe, lo
cual provoca en los usuarios no hispanoparlantes un cierto
recelo en su uso. Esta dificultad está siendo trabajada en
las últimas versiones CVS, aún por completar.
4)Muchos usuarios se quejan de la lentitud del
intérprete ya que todas las rutinas gráficas se ejecutan por
software. Aunque se ha hablado de posibles versiones
aceleradas a través de arquitecturas OpenGL y parece que esta
va a ser la vía que se siga en un futuro, aún no hay nada que
pueda ser mostrado.
5)Por último, no existen IDEs completos para plataformas
no-Windows, lo que conlleva cierta dificultad en su uso para
el resto de plataformas. Recientemente ha aparecido un
entorno escrito en Gambas que pretende facilitar el trabajo a
los usuarios de Linux. Sin embargo, aún no es más que un
editor simple con soporte para iluminación de sintaxis. (Por
si no lo sabéis, un IDE no es más que un programa que integra
en un solo entorno el editor, el intérprete, el compilador,
el depurador si lo hubiera, la ayuda del lenguaje,etc; de
manera que todas las herramientas necesarias para el
programador están accesibles inmediatamente dentro de ese
entorno de forma coherente,cómoda y sobretodo, visual. Si no
se trabaja dentro de un IDE, las diferentes herramientas –que
en el caso de Fénix son de consola (es decir, ejecutables vía
línea de comandos –“la ventana negra del MS-DOS”- y por tanto
no visuales)- son para el desarrollador programas
independientes, inconexos entre sí, con lo que la utilización
conjunta de uno u otro és más pesada e incómoda, y no es
visual). La explicación de que no haya un IDE oficial que
acompañe a las versiones oficiales del lenguaje es porque el
compilador/intérprete Fenix son programas multiplataforma, y
su entorno oficial debe obligatoriamente funcionaren
cualquier plataforma donde funcione Fenix. El mejor modo de
68
conseguir esto es programar dicho entorno en el propio Fenix,
seguramente empleando alguna DLL a medida con funciones
comunes. Los autores de Fenix dicen que están escribiendo
rutinas de interfaz de usuario (botones, cuadros de diálogo,
cajas de texto, etc) para la versión 1.0, mediante las cuales
escribirán utilidades en el propio Fenix para gestionar los
tipos de fichero reconocidos por él, y en el futuro, extender
estas rutinas hasta el punto que sea posible editar, compilar
y depurar programas en Fenix desde el propio Fenix. Sin
embargo, avisan que no será hasta después de la versión 1.0.
69
- Precisión completa de 0 a 255 para las
componentes de los colores, lo que permitiría usar mapas
de durezas de forma preciso (ya veremos lo que es eso de
mapa de durezas).
- Misma codificación de color para todas las
tarjetas, lo que significa que un número dado representa
el mismo color en todas partes.
- Más facilidad para escribir rutinas que hagan
operaciones de color (transparencias pixel a pixel,
transformaciones, etc)
70
Obtención,instalación y funcionamiento de Fénix
71
3º)Una vez obtenido este archivo .DCB, cada vez que
queramos ejecutar nuestro juego tendremos que
interpretar dicho archivo mediante FXI.EXE, y así poner
en marcha nuestro programa. ¡Y ya está!, esto es todo lo
que necesitas para programar en Fenix.
72
que ofrecen modularidad y funcionalidad extra al núcleo del
lenguaje Fénix, las cuales están listas para incluir en
nuestros videojuegos y aprovechar sus comandos definidos.
Verás que hay, en la versión 0.84b de Fénix, una librería
pensada para facilitar la creación de explosiones, o otra
librería para facilitar la visualización de videos MPEG1. Ya
hablaremos de ellas en capítulos posteriores.
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
73
Ahora abre la línea de comandos de Windows. Si no te
acuerdas cómo se hacía, ve al menú Inicio->Ejecutar y escribe
“cmd.exe” (sin comillas). Verás que aparece la típica ventana
negra. Muévete a la carpeta donde tienes Fenix ( acuérdate
del comando cd...). Esto es muy importante porque si no te
mueves a dicha carpeta, seguramente tendrás errores y no
podrás hacer nada. Una vez allí, pues, escribe
fxc <miprograma>.prg
fxi <miprograma>
74
cualquier ruta donde estuvieras si tienes la precaución de
escribir ANTES de hacer nad a la siguiente línea:
PATH=%PATH%;x:\carpeta\de\Fenix
75
Un aspecto curioso respecto el tema de la compilación es
que disponemos de la posibilidad de crear “stubs”. Y esto
¿qué es? Pues generar a partir de los archivos ya creados PRG
y DCB un archivo EXE. Este archivo EXE incorporará en su
interior el intérprete FXI.exe más el código DCB del juego.
Es decir, que en vez de tener el intérprete por un lado y el
código compilado por el otro, podremos tener las dos cosas
juntas (el intérprete “con mochila”) en un ejecutable, de tal
manera que nuestro juego se pueda distribuir un único
archivo. Para lograr esto, si suponemos que tenemos un
archivo “a.prg” y su correspondiente “a.dcb”, para generar el
archivo “a.exe” deberíamos escribir fxc –s a.dcb a.fpg.
El IDE FlameBird2
76
De entre los IDEs existentes, creo que el más completo
que puedes encontrar para Windows es el FlameBird2. Puedes
bajarlo de la web oficial: http://fbtwo.sf.net O también de
FenixWorld: http://fenixworld.se32.com.
77
Puedes probar de escribir, grabar el prg en una carpeta
cualquiera y ejecutar el mismo programa de antes. Verás que
es mucho más cómodo porque lo tienes todo a mano.
78
Sobre el uso de la ayuda de Fénix
79
las imágenes individualmente –con load_png por ejemplo,
tendrás que incluir en el paquete una a una las imágenes PNG
por separado (del tema imágenes ya hablaremos más adelante).
Además, si incluyes sonidos, también tendrás que meterlos
dentro del paquete a distribuir, incluyendo pues cada uno de
los archivos WAV,OGG,MOD,PCM,XM… individuales. Lo mismo si
has utilizado videos.
80
“Pepito.exe”. Y ya está. El jugador potencial hará clic en el
único exe que ve y ahora sí, el juego se ejecutará sin más
problema.
81
Y de regalo, tu segundo programa en F énix
Program Misegundoprograma;
private
int mivar1;
end
begin
loop
delete_text(0);
mivar1=rand(1,10);
if (mivar1<=5)
write(0,200,100,2,"Menor o igual que
5");
else
write(0,200,100,2,"Mayor que 5");
end
frame;
end
end
http://fenix.divsite.net
Web oficial de Fenix. Desde aquí podrás descargarte las
fuentes y la versión compilada de Fenix para las diferentes
plataformas disponibles (Windows, Linux, BeOs…), en su enlace
de descargas.
82
http://forum.divsite.net
Foro Español de Fenix donde la comunidad participa
activamente.Muy recomendable. La cantidad de información que
se reúne en sus post es de un valor incalculable. La
asiduidad de sus miembros y la gran voluntad en aportar
nuevas ideas y soluciones y ayudar a los más novatos lo
convierten un lugar permanente de intercambio.
http://fenix.divsite.net/doc/es
Referencia Online del lenguaje. Aquí vienen explicadas todas
las especificaciones sobre cada una de las funciones del
lenguaje Fenix (qué parámetros usan, qué devuelven, para qué
sirven,etc), sobre las diferentes variables (de qué tipo son,
para qué sirven, qué valores pueden tener), sobre las
palabras claves del lenguaje (construcción de bucles,
condicionales, declaración de variables, creación de
procesos,etc), incluso códigos de ejemplo que muestran el uso
de l as funciones.
Es una visita obligada para todo aquél que quiera
programa en Fenix. Repito, consultarla es IMPRESCINDIBLE. De
hecho, recomiendo descargarla en el disco duro local para
poderla tener más a mano. Para realizar la descarga de
múltiples páginas web de un mismo sitio podemos recurrir a
aplicaciones como WebCopier (http://www.maximuxsoft.com )
-comercial- , o HTTrack (http://www.httrack.com ) –libre-, o
incluso Acrobat ( http://www.adobe.com ), el cual convertirá
toda la referencia en un único pdf muy práctico.
http://fenixworld.se32.com
Portal Español especializado en Fenix. Es el punto de reunión
de los desarrolladores en Fénix, donde se intercambian
impresiones, trucos y donde se cuelgan los proyectos
acabados. En esta web hay un apartado donde también se pueden
descargar diferentes herramientas que nos pueden ayudar a la
hora de programar en Fénix, como pueden ser “FPG Edit” y “FNT
Edit”, las cuales utilizaremos en este curso.
http://fbtwo.sf.net
Web oficial de FlameBird2, el IDE de Fenix que utilizaremos
en este curso, desde donde lo podremos descargar.
http://www.tutorialgames.net
Excelente portal dedicado a la difusión y aprendizaje de
distintos lenguajes de programación de videojuegos, como el
Blitz3D, cDiv o el propio Fénix. Muy recomendable su apartado
de códigos fuente de ejemplo donde se pueden observar
verdaderas maravillas.
83
http://www.flamingbird.com/
Para la documentación inglesa. También tiene una sección de
tutoriales muy interesante, y sobre todo, la sección de
descargas, donde están disponibles multitud de librerías DLL
que aportan funcionalidad extra al lenguaje Fénix (la
Lib3Dm8e.dll, la LibDPlay.dll, la LibOdbc.dll, la
LibRegistry.dll,la WPF.dll,la VTE.dll,la net.dll,etc) además
de varios IDEs aparte del FlameBird2, el FPGEdit, y el propio
Fénix en sus versiones para diferentes plataformas.
http://cvs.sf.net/cvstarballs
Lugar de donde puedes descargar el código fuente (escrito en
ANSI C) más actualizado de Fénix hasta la fecha, para poderlo
compilar directamente en tu ordenador.
De esta manera, además de producir los binarios de Fénix
más optimizados para tu máquina particular,tienes la
posibilidad de conseguir la versión última al día con todos
los pequeños cambios que Fénix ha sufrido desde la última
versión “oficial”. Estos cambios normalmente no se ven
reflejados en un paquete zip descargable de la web de Fénix
hasta mucho tiempo después, cuando sale la siguiente versión
“oficial”.
84
CAPÍTULO 2: EMPEZANDO A PROGRAMAR CON FÉNIX
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
Program MiPrimerPrograma;
85
de programación) y que por tanto, se ha de escribir tal cual
–aunque recordad que Fenix es “case-insensitive”-.
Comentarios:
/*Y éste
comentario es
un comentario
de varias*/
Private
int mivar1;
End
Begin
mivar1=10;
while(mivar1<320)
86
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
Bloque “Private/end”:
87
global, habría que reemplazarla por “global”. Nada nos impide
pode r declarar variables de estos tres tipos diferenciados en
un mismo programa, lo cual en general tendría finalmente el
siguiente aspecto:
Program miprograma;
Private
//Declaración de variables privadas
End
Local
//Declaración de variables locales
End
Global
//Declaración de variables globales
End
Begin
//Programa principal
End
88
siempre qué tipo de valores puede o no albergar una variable
determinada. No es lo mismo que una variable sea declarada
para poder guardar números enteros que para guardar cadenas
de cara cteres. Si declaramos una variable como entera, si
quisiéramos asignarle un valor de cadena de caracteres daría
un error. Así pues, siempre, cuando se declara la variable,
hay que decirle que será de un tipo determinado, y que por
tanto, los valores que incluirá, sean los que sean, siempre
serán de ese tipo determinado. Dependiendo de lo que queramos
hacer con esa variable, nos convendrá crearla de un tipo o de
otro, según lo que tengamos previsto almacenar en ella.
tipo nombre;
89
tipo nombre= valor;
int mivar1=10;
90
variables personales “x”, porque se podría confundir con la
predefinida, aunque no la estemos usando. Hay que decir que
para utilizar las variables predefinidas no hay que
declararlas ni nada. Profundizaremos en esto.
Bloque “Begin/end”:
Program MínimoPrograma;
Begin
End
Línea “mivar1=10;”:
x = 123;
91
Los programas pueden también consultar el valor de las
variables, o utilizarlos en diferentes expresiones; por
ejemplo, para poner en una variable entera llamada "y" el
valor de "x" más 10, se uti lizaría una sentencia como la
siguiente:
y = x + 10;
Bloque “While/End”:
92
While (condición)
…
End
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
93
vez a la línea del while y se vuelve a comprobar la condición
a ver si continúa siendo verdadera. Si “mivar1” continúa
siendo menor que 320, se vuelve a ejecutar el interior del
bucle, y así, y así hasta que llegue un momento que a la hora
de efectuar la comparación otra vez, “mivar1” ya no valga
menos que 320. En ese caso, el programa salta todas las
líneas internas del bucle y continúa su ejecución justo
después del end del while. Se supone que en algún momento
durante la ejecución de las sentencias del interior del
bucle, el valor de “mivar1” cambiará, porque si no cambiara,
“mivar1” siempre valdría 10 y la condición siempre sería
verdadera, por lo que siempre se ejecutarían las sentencias
del interior del bucle y eso daría lugar a un bucle infinito:
el programa no se acabaría nunca.
Orden “Delete_text”:
94
números/letras/palabras/signos… separados por comas, o haber
tres números/letras/palabras/signos… separados por comas, o
haber cuatro,etc. Cada uno de los valores que aparecen dentro
de los paréntesis se denominan parámetros de ese comando, y
el número de ellos y su tipo (si son números, letras,
palabras,etc) depende de cada comando en particular. ¿Y para
qué sirven los parámetros? Para modificar el comportamiento
del comando en algún aspecto.
95
hemos dicho ya: que delete_text se dedicará a borrar TODOS
los textos que aparezcan por pantalla. Poniendo otros valores
como parámetros –ya lo veremos-, se podría especificar más y
decirle a delete_text que borrara tal o cual texto concreto.
Línea “mivar1=mivar1+2;”:
96
el valor 30 a la variable mivar1 (y eso siempre es verdad), y
por lo tanto, nunca se incumpliría una condición que de hecho
no es tal.
Orden “Write”:
97
-1r parámetro (de tipo entero): Indica un código
correspondiente a la fuente de letra que se va a utilizar
para el texto.Si no queremos usar ninguna fuente concreta, la
fuente predeterminada del sistema se representa por el código
0.
-2º parámetro (de tipo entero): coordenada
horizontal del texto en píxeles
-3r parámetro (de tipo entero): coordenada vertical
del texto en píxeles
-4º parámetro (de tipo entero): código
correspondiente al tipo de alineación. Indica cómo se han de
interpretar las coordenadas del 2º y 3r parámetro. Por
ejemplo, si este 4º parámetro vale 0, la coordenada escrita
en el 2º y 3r parámetro corresponderá al extremo superior
izquierdo del texto; si este 4º parámetro vale 2, la
coordenada corresponderá al extremo superior derecho,si vale
4, el texto estará centrado horizontalmente y verticalmente
alrededor de ese punto, si vale 6, la coordenada
corresponderá al extremo inferior izquierdo,etc,etc. Consulta
la referencia del lenguaje (en http://fenix.divsite.net) para
ver los demás valores.O más rápido, jugar con la función y
ver los resultados: hay hasta el valor 8.
98
descubrimiento!-, con la fuente predeterminada del sistema
(todavía no sabemos manejar con Fénix los diferentes tipos de
fuente disponibles), y con la alineación de tipo 1, que
quiere decir que la coordenada horizontal especificada en
write estará en el centro del texto y la coordenada vertical
será el “techo” justo debajo del cual se escribirá el
mensaje.
Orden “Frame”:
99
De esta manera, un programa Fénix se basa en trozos de
código que se van ejecutando pero que realmente no se ven sus
resultados en pantalla hasta que llega un Frame. A partir de
él, si hay más líneas de código se vuelven a ejecutar, pero
sin llegar a visualizarse hasta que no se encuentre con el
siguiente Frame. Y así.
100
Funcionamiento global del programa:
101
poner en el while 320 ponemos 32? Pues ocurriría lo
contrario: el programa acabaría antes de que el texto pudiera
llegar al extremo derecho, de hecho acabaría enseguida porque
el texto pronto llega a esa coordenad a.
102
hacer un scrol l en diagonal, simplemente añadiendo otra
variable y poniendo las dos variables en el 2º y 3r parámetro
del “write”. Algo así como esto:
Program MiPrimerPrograma;
Private
int mivar1;
int mivar2;
End
Begin
mivar1=10;
mivar2=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
mivar2=mivar2+2;
write(0,mivar1,mivar2,1,"¡Hola mundo!");
frame;
end
end
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=240;
while(mivar1>10)
delete_text(0);
mivar1=mivar1-2;
write(0,100,mivar1,1,"¡Hola mundo!");
frame;
end
end
103
Lo que he hecho ha sido: primero, darle un valor inicial a
“mivar1” de 240; segundo, cambiar la condición del while
poniendo que el bucle se acabará cuando “mivar1” deje de ser
mayor que 10; y tercero, el incremento de la “mivar1” lo hago
negativo. Con estos tres cambios, lo que consigo es que
inicialmente el texto se escriba en el extremo inferior de la
ventana, y que en cada iteración se vaya escribiendo 2
píxeles más arriba –recordad que la coordenada vertical
decrece si vamos subiendo por la ventana- y este proceso se
irá ejecutando hasta que lleguemos al momento donde “mivar1”
vale menos de 10, es decir, que el texto se ha escrito casi
en el extremo superior de la ventana.
Notas finales:
104
Otra cosa que posiblemente no hayas visto es cuándo en
general se escriben los puntos y coma y cuándo no. En
general, si no digo explícitamente lo contrario, los puntos y
coma hay que escribirlos SIEMPRE al final de una sentencia
(como puede ser un comando, una asignación, o cualquier
cosa). La única excepción –que de hecho no es tal porque no
son sentencias - es cuando abrimos y cerramos un bloque de
código, como puede ser un bucle (WHILE/END es un ejemplo) ,
un condicional (IF/END…) o el bloque principal del programa
(BEGIN/END). En general, todas las parejas de palabras que
tengan la palabra END no llevan punto y coma.
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end //End del while
end //End del begin
105
Explicación paso a paso de “Mi segundo programa en
Fénix”
Recordemos el código:
Program Misegundoprograma;
private
int mivar1;
end
begin
loop
delete_text(0);
mivar1=rand(1,10);
if (mivar1<=5)
write(0,200,100,2,"Menor o igual que 5");
else
write(0,200,100,2,"Mayor que 5");
end
frame;
end
end
loop
delete_text(0);
mivar1=rand(1,10);
if (mivar1<=5)
write(0,200,100,2,"Menor o igual que 5");
else
write(0,200,100,2,"Mayor que 5");
end
frame;
end
106
Bloque “Loop/End”. Sentencias BREAK y CONTINUE:
*Sentencia BREAK;:
*Sentencia CONTINUE;:
107
evaluación de la siguiente. Aparte del bucle LOOP,puede
usarse esta sentencia en los bucles WHILE,REPEAT, FOR o FROM
(ya los veremos). Por ejemplo, una sentencia CONTINUE dentro
de un bucle WHILE forzará al programa a comprobar la
condición inicial inmediatamente, y si ésta es cierta,
volverá a ejec utar las sentencias interiores desde el
principio (tras el WHILE), y si la condición resultara falsa,
la senetencia CONTINUE finalizará el bucle.
Program ejemplo;
Private
Int mivar1=0;
End
Begin
Loop
mivar1=mivar1+1;
If (mivar1==30)
Break;
End
write_var(0,100,100,4,mivar1);
Frame;
End
End
108
comprobación está antes del “write”, y por tanto, cuando
“mivar1” sea igual a 30, se saldrá del bucle sin pasar por el
“write”, y por tanto, el valor 30 no se escribirá. Otra cosa
sería –es fácil verlo-, si hubiéamos escrito el mismo
programa pero cambiando el orden:
Program ejemplo;
Private
Int mivar1=0;
Begin
Loop
mivar1=mivar1+1;
write_var(0,100,100,4,mivar1);
If (mivar1==30)
Break;
End
Frame;
End
End
109
Program ejemplo;
Private
Int mivar1=0;
Begin
Loop
mivar1=mivar1+1;
If (mivar1==30)
continue;
End
write_var(0,100,100,4,mivar1);
Frame;
End
End
110
“mivar1=rand(1,10);”.Valores de retorno de las funciones:
Program ejemplo;
Private
Int mivar1;
End
Begin
Loop
mivar1=write(0,100,100,4,”Hola”);
write_var(0,150,150,4,mivar1);
Frame;
End
End
111
hacíamos porque no nos interesaba, aunque write lo devuelve
siempre - en una variable, para seguidamente visualizar dicho
valor también por pantalla.
Program ejemplo;
Private
Int mivar1;
Begin
mivar1=write(0,100,100,4,"Hola");
write_var(0,150,150,4,mivar1);
Loop
Frame;
End
end
112
Deberías entender lo que ocurre. Escribimos una sola vez
“Hola”, vemos como el identificador de ese texto,
efectivamente es el número 1 (siempre se crean los
identificadores a partir del 1 para el primer texto escrito y
a partir de allí para arriba) y es sólo después cuando nos
metemos en el bucle principal del programa con el frame, para
que se pueda visualizar la frase fija todas las veces que
haga falta.
Otra solución al mismo problema sería:
Program ejemplo;
Private
Int mivar1;
Begin
Loop
delete_text(0);
mivar1=write(0,100,100,4,"Hola");
write_var(0,150,150,4,mivar1);
Frame;
End
end
Program ejemplo;
Private
Int mivar1;
Begin
Loop
delete_text(1);
mivar1=write(0,100,100,4,"Hola");
113
write_var(0,150,150,4,mivar1);
Frame;
End
end
mivar1=rand(1,10);
114
Es decir, en general, la sintaxis del bloque IF/ELSE
será:
IF ( condición )
Sentencias -una o más-que se ejecutan si la condición es
verdad;
ELSE
Sentencias –una o más- que se ejecutan si la condición
es falsa;
END
IF ( condición )
Sentencias -una o más-que se ejecutan si la condición es
verdad;
END
== Comparación de igualdad
<> Comparación de diferencia (también sirve !=)
> Comparación de mayor que.
>= Comparación de mayor o igual que (también sirve =>)
< Comparación de menor que.
<= Comparación de menor o igual que (también sirve =<)
115
lógicos son:
Program Misegundoprograma;
private
int mivar1;
end
begin
loop
delete_text(0);
mivar1=rand(1,10);
116
if (mivar1<=5)
write(0,200,100,2,"Menor o igual que 5");
else
write(0,200,100,2,"Mayor que 5");
end
frame;
end
end
Está claro que lo que hace el programa es, una vez que
la función rand ha devuelto un número aleatorio entre 1 y 10
–ambos incluidos- y lo ha asignado a la variable “mivar1”,
comprueba el valor de dicha variable con un if. Si el recién
valor de “mivar1” es menor o igual que 5, saldrá un texto en
pantalla, en una coordenada concreta que dirá “Menor o igual
que 5”. En cambio, si “mivar1” es mayor que 5, saldrá otro
texto, en las mismas coordenadas, que diga “Mayor que 5”.
Y ya está.
117
ejecutado. Llegado este punto, la iteración acaba y
comenzamos una nueva iteración: borramos el tex to, asignamos
a la variable un nuevo valor aleatorio que no tendrá nada que
ver con el que tenía antes, y se vuelve a hacer la
comprobación. Como los valores que tendrá “mivar1” en las
distintas iteraciones son aleatorios, unas veces serán
mayores o iguales que 5 y otras veces serán menores que 5,
por lo que en algunas iteraciones aparecerá una frase y otras
la otra, mostrando así el efecto que vemos por pantalla: la
frase cambia rápidamente de una en otra casi sin tiempo para
leerla. Y así hasta el infinito.
Bloque “SWITCH/END”:
118
END
CASE valores:
Sentencias;
END
…
DEFAULT:
Sentencias;
END
END
119
Un ejemplo:
program pepito;
private
byte mivar1=220;
end
begin
loop
delete_text(0);
switch (mivar1)
case 0..3:
write(0,200,100,4,"La variable vale entre
0 y 3, incluidos");
end
case 4:
write(0,200,100,4,"La variable vale 4");
end
case 5,7,9:
write(0,200,100,4,"La variable vale 5 o 7
o 9");
end
default:
write(0,200,100,4,"La variable vale
cualquier otro valor diferente de los anteriores");
end
end // switch
frame;
end //loop
end // begin
Bloque “FOR/END”:
FOR(valor_inicial_contador;condicion_final_bucle;incrmen
t_cont)
Sentencias;
END
120
valor inicial de la variable que va a utilizarse como
contador de iteraciones del bucle.Un ejemplo puede ser
la sentencia de asignación x=0; que fijaría la variable
x a cero a inicio del bucle (valor para la primera
iteración).
121
cualquiera resultara falsa, el bucle finalizaría), las
sentencias interiores, y al final, tras cada iteración, todos
los incrementos.
Un ejemplo:
122
interio r del FOR. Una vez hecho esto, se vuelve para arriba,
se incrementa “mivar1” para que valga 3, se vuelve a
comprobar la condición, y si es verdadera, se vuelve a
ejecutar el interior del FOR, y así hasta que llegue una
iteración donde al comprobar la condi ción ésta sea falsa. En
ese momento, la ejecución del programa seguirá en la línea
inmediatamente posterior al END del FOR.
123
tiene el programa, y que viene marcado por FRAME: cada vez
que se “abre la puerta”, se marca el ritmo de impresión por
pantalla. De ahí el nombre al comando FRAME: cada vez que se
llega a ese comando es como gritar “¡Mostrar fotograma!”.
124
justamente. Es decir, si eliminamos el frame, el bucle se
realizará igual, iteración a iteración, pero hasta que no
encuentre un frame no va a poder visualizar el resultado de
sus cálculos. Por eso, cuando el programa entra en el Loop y
se e ncuentra un frame (el primero), pone en pantalla de golpe
todo aquello que llevaba haciendo hasta entonces, que es
precisamente todas y cada una de las iteraciones del bucle
FOR.
mivar1=10;
write(0,100,100,4,”Esta variable vale:” + mivar1);
donde puedes ver que el signo + sirve para poder unir trozos
de frases y va lores separados para que aparezcan como un
único texto. La gran diferencia que hay entre write y
write_var es que con el primero, una vez que se escribe el
valor de la variable, éste pasa a ser un valor fijo en
pantalla como cualquier otro y no se puede cambiar. En
cambio, con write_var, los valores de las variables que se
hayan escrito, a cada fotograma se comprueba si han cambiado
o no, y si lo han hecho, se actualiza automáticamente su
valor. Es decir, que con write_var los valores impresos de
las variables cambiarán al momento que dichos valores cambien
en el programa, permanentemente. Lo puedes comprobar: si
cambiar la función write por write_var, verás que a cada
iteración se va imprimiendo un nuevo número, pero los números
impresos anteriormente también cambian al nuevo valor: esto
es así porque como estamos cambiando los valores a “mivar1”,
todos los valores de esa variable, impresos en cualquier
momento de la ejecución del programa cambiarán también a cada
fotograma que pase.
125
¿verdad? La clave está en darse cuenta que “mivar1” llega a
valer 10 en el momento de salir del FOR. Y como seguidamente
nos encontramos con un LOOP, este bucle nos permite continuar
con la ejecución del programa, y por tanto, permite la
aparición de sucesivos fotogramas. Como he dicho que a cada
fotograma del programa se realiza una comprobación y
actualización automática de todos los valores impresos por
write_var, como en ese momento “mivar1” vale 10, cuando se
llega la primera vez al FRAME del LOOP, se llega a un
fotograma nuevo, y así, se realiza la actualización masiva de
todos los números. A partir de allí, como el programa ya
únicamente consiste en ir pasando fotogramas y ya está, el
valor 10 de “mivar1” va a continuar siendo el mismo y ya no
se ve ningún cambio en pantalla.
program pepito;
private
byte mivar1;
end
begin
mivar1=1;
while (mivar1<10)
write(0,(mivar1*25)+30,100,4,mivar1);
mivar1=mivar1+1;
frame(2000);
end
loop
frame;
end
end
program nuevo;
private
int a,b ;
end
begin
126
for (a=0,b=10;a<5 AND b>5;a=a+1,b=b-1)
write(0,0,10*a,0,"a="+a+" b="+b) ;
end
loop
frame;
end
end
Bloque “REPEAT/UNTIL”:
REPEAT
Sentencias;
UNTIL ( condición )
127
la falsedad de la condición hasta después se haber ejecutado
dichas sentencias interiores.
program ej1;
private
byte mivar1=1;
end
begin
while(mivar1<1)
write(0,100,100,4,mivar1);
frame;
end
loop
frame;
end
end
program ej2;
private
byte mivar1=1;
end
begin
repeat
write(0,100,100,4,mivar1);
frame;
until (mivar1<1)
loop
frame;
end
end
128
Para acabar, decir que na sentencia BREAK dentro de un
bucle REPEAT lo finalizará de forma inmediata, continuando el
programa a partir de la sentencia siguiente a dicho bucle.Una
sentencia CONTINUE dentro de un bucle REPEAT forzará al
programa a comprobar la condición del UNTIL inmediatamente, y
si ésta es falsa, volverá a ejecutar las sentencias
interiores desde el principio (tras la palabra reservada
REPEAT). Si la condición resulta cierta, la sen tencia
CONTINUE finalizará el bucle.
Bloque “FROM/END”:
129
iteración siempre que el valor de la variable no haya llegado
(o sobrepasado) el valor final del bucle.
Primer ejemplo :
program ejemplo1;
private
int mivarX=33;//Un valor inicial cualquiera
int mivarY=174; //Un valor inicial cualquiera
end
begin
130
set_mode(320,200,16);
loop
mivarX=rand(0,320);
mivarY=rand(0,200);
if(mivarX>100 and mivarX <250 and mivarY>50 and
mivarY< 150)
continue;
end
write(0,mivarX,mivarY,4,".");
frame;
end
end
Segundo ejemplo:
program ejemplo2;
private
int columna;
int fila;
end
begin
set_mode(320,200,16);
for(columna=1;columna<=5;columna=columna+1)
for(fila=0;fila<5;fila=fila+1)
write(0,(columna*20)+30,(fila*20)+30,4,"*");
frame;
end
end
loop
frame;
end
end
131
Explico un poco el código: fíjate que hay dos FOR
anidados: esto implica que por cada iteración que se produzca
del FOR externo for(columna=1;…) -en total son 5: de 1 a 5-,
se realizarán 5 iteraciones del FOR interno for(fila=1;…) –de
0 a 4-, por lo que se acabarán haciendo finalmente 25
iteraciones con los valores siguientes: columna=1 y fila=0,
columna=1 y fila=1, columna=1 y fila=2, columna=1 y fila=3,
columna=1 y fila=4, columna=2 y fila=0, columna=2 y fila=1,
etc. Luego ya sólo basta imprimir el signo * en las
coordenadas adecuadas para dar la apariencia de cuadrado.
program ejemplo2;
private
int columna;
int fila;
end
begin
set_mode(320,200,16);
for(columna=1;columna<=5;columna=columna+1)
for(fila=1;fila<=columna;fila=fila+1)
write(0,(columna*20)+30,(fila*20)+30,4,"*");
frame;
end
132
end
loop
frame;
end
end
program ejemplo2;
private
int columna;
int fila;
end
begin
set_mode(320,200,16);
for(fila=1;fila<=5;fila=fila+1)
for(columna=1;columna<=fila;columna=columna+1)
write(0,(columna*20)+30,(fila*20)+30,4,"*");
frame;
end
end
loop
frame;
end
end
program ejemplo2;
private
int columna;
int fila;
end
begin
133
set_mode(320,200,16);
for(fila=5;fila>=1;fila=fila-1)
for(columna=5;columna>=fila;columna=columna-1)
write(0,(columna*20)+30,(fila*20)+30,4,"*");
frame;
end
end
loop
frame;
end
end
program ejemplo2;
private
int columna;
int fila;
end
begin
set_mode(320,200,16);
for(fila=0;fila>=1;fila=fila-1)
for(columna=5;columna>=fila;columna=columna-1)
write(0,(columna*20)+30,(fila*20)+30,4,"*");
frame;
end
end
loop
frame;
end
end
Tercer ejemplo
134
número ha de ser la suma de un 1 con ese 2. Por tanto, 3. Y
así.Es fácil ver, por tanto, que el resultado que queremos
que obtenga nuestro programa ha de ser el siguiente:
program ejemplo3;
private
int num=1;
int num2=1;
int suma;
int cont;
end
begin
set_mode(640,480,16);
//Imprimimos los dos primeros números de la serie (1 y 1)
write(0,200,10,4,num);
write(0,200,20,4,num2);
for(cont=3;cont<=20;cont=cont+1)
suma=num+num2;
write(0,200,cont*10,4,suma);
/*Corremos los números."num" se pierde,"num2" pasa
a ser "num" y "suma" pasa a ser "num2"*/
num=num2;
num2=suma;
frame;
end
loop
frame;
end
end
135
CAPÍTULO 3: INCLUSIÓN DE GRÁFICOS
Todos los ejemplos que hemos hecho hasta ahora no han
incluido ningún tipo de gráfico: sólo textos. Es evidente que
para diseñar un juego necesitarás gráficos, además de
posiblemente música y otros efectos. En este capítulo se
tratará las diferentes mane ras de incluir los gráficos de
nuestro juego.
Program ejemplo;
Begin
Set_mode(640,480);
Loop
Frame;
end
End
136
El tercer parámetro de set_mode puede tener dos valores:
el número 8 o el número 16. Este parámetro sirve para indicar
la profundidad de color a la que funcionará el juego. Fíjate
que un juego en Fénix sólo puede usar pues la profundidad de
8 bits (256 colores) o 16 bits (color de alta densidad). Una
profundidad de 16 es más que suficiente para alcanzar todas
las tonalidades que el ojo humano es capaz de distinguir, así
que en principio no es imprescindible profundidades mayores,
las cuales acarrearían más carga de procesamiento por parte
del intérprete. Ya que este parámetro es opcional, si no se
escribe nada se sobreentiende que la profundidad será de 8
bits.
137
Si esta función crea una ventana de juego, utilizará por
título el nombre del fichero DCB y un icono est ándar. La
función set_title establece el título de la ventana del juego
(el cual se pasa entre comillas como el único parámetro que
tiene), y la función set_icon usa un gráfico determinado como
icono para mostrar en la barra de título de la ventana del
juego ( el funcionamiento de esta función lo estudiaremos
posteriormente, cuando hayamos introducido el u so de
gráficos).Es recomendable siempre usar estas funciones
(set_title y set_icon) inmediatamente antes de la llamada a
set_mode.
Program ejemplo;
Begin
Set_mode(640 ,480,MODE_16BITS,MODE_MODAL +
MODE_FRAMELESS );
Loop
Frame;
end
End
Program ejemplo;
Begin
Set_title(“Prueba”);
Set_mode(640 ,480,MODE_16BITS,MODE_MODAL);
Loop
Frame;
end
End
138
Configuración de los frames per second:
139
rendimiento general que están consiguiendo el equipo de
desarrollo de Fénix, es fácil que puedas usar los 60 FPS en
casi todos tus juegos sin problemas.
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
while(mivar1<320)
140
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
Set_fps(60,1);
mivar1=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
Set_fps(1,1);
mivar1=10;
while(mivar1<320)
delete_text(0);
141
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
142
http://fenixworld.se32.com/download.php?view.36 , o también
en SourceForge:
http://osdn.dl.sourceforge.net/sourceforge/cdiv . Está en
proyecto incorporar al paquete estándar una aplicación de
creación de FPG por línea de comandos, estilo FXI.EXE o
FXC.EXE, pero de momento no está disponible. El FPGEdit es
muy sencillo de utilizar: basta recorrer el disco duro
mediante el árbol que aparece a la izquierda de la ventana
del programa en busca de las imágenes que queremos incluir en
el FPG y seleccionarlas. Irán apareciendo listadas en la
parte superior de la ventana del programa.Seguidamente, hemos
de decirle que queremos crear un nuevo fichero FPG e
introducir en él los gráficos que deseemos.Esto se hace
utilizando la parte inferior de la ventana del programa,
donde crearemos el FPG. En el cuadro que aparece cuando
queremos crear un FPG nuevo, escogeremos la ruta donde se
grabará el archivo FPG con el nombre que queramos y como tipo
de FPG el de 16 bits de Fénix y a partir de ahí iremos
arrastrando y soltando los gráficos que deseamos incluir,
desde la parte superior de la ventana hasta allí. Fíjate, es
importante, que cada gráfico, una vez incluido en el FPG,
tendrá asociado un número de tres cifras. Este número será el
identificador único de ese gráfico en aquel código fuente
donde se utilice el FPG. Es decir, que cuando se quiera hacer
referencia a ese gráfico en el código, aparte de decir en qué
FPG está incluido, habrá que decir cuál es el número concreto
que corresponde al gráfico buscado. Como podrás deducir,
“sólo” caben hasta 999 gráficos en un FPG(el 000 no vale), y
puedes comprobar que ese código lo puedes cambiar según tus
intereses, siempre que mantengas que es un número único para
ese FPG. Una vez hayamos seleccionado los gráficos que
queríamos, hemos de guardar el fichero FPG.Y siempre podremos
abrir un archivo FPG ya creado para editarlo: quitar o poner
nuevas imágenes o cambiar sus códigos identificativos,etc.
143
Una vez que tengas creada la imagen de fondo, (y de
hecho, todas las imágenes que vayas a utilizar en el juego),
lo más cómodo es que la/s guardes en la misma carpeta donde
tienes el PRG que estarás haciendo.Así no habrá problemas de
rutas y se encontrarán las imágenes inmediatamente.
Program MiPrimerFondo;
private
int id1;
end
Begin
set_mode(600,400,16);
id1=load_png("fondo.png");
put_screen(0,id1);
loop
frame;
end
end
144
es un proceso necesario pero que es interno del programa, en
la pantalla no se va a ver nada.
id1=load_png(“imagenes\fondo.png”);
id1=load_png(“C:\Juego\imagenes\fondo.png”);
145
centro de la pantalla, independientemente de la resolución,
por eso normalmente la imagen usada con esta función ocupa
toda la pantalla o gran parte de ella.Si no llegara a ocupar
toda la pantalla lo que falta se rellena en negro.
Program MiSegundoFondo;
Private
int id2;
end
Begin
set_mode(640,480,16);
id2=load_fpg(“prueba.fpg”);
put_screen(id2,1);
Loop
Frame;
End
end
146
por load_fpg. Siempre que carguemos el FPG, cuando queramos
hacer referencia a alguna de las imágenes que contiene,
tendremos primero que indicar en qué FPG se encuentra, y
posteriormente decir qué código de imagen tiene dentro de él.
Es lo que hacemos aquí: decimos que queremos cargar una
imagen que está en el fichero identificado por “id2 cuyo
código interno es 001 (el valor del segundo parámetro). Es
por esta razón que cuando cargamos las imágenes directamente,
el primer parámetro vale 0, porque no necesitamos referenciar
a ningún FPG.
Descarga de imágenes:
Program MiSegundoFondo;
Private
int id2;
end
147
Begin
set_mode(640,480,16);
id2=load_fpg(“prueba.fpg”);
put_screen(id2,1);
Loop
if (key(_esc))
break;
end
Frame;
End
end
if (key(_esc))
break;
end
148
programa. ¡Acabamos de descubrir una nueva forma de acabar el
programa que no sea dándole al botón de Cerrar de la
ventana!: pulsando una tecla determinada podemos hacer lo
mismo.
149
_CAPS_LOCK
_F1
_F2
_F3
_F4
_F5
_F6
_F7
_F8
_F9
_F10
_NUM_LOCK
_SCROLL_LOCK
_HOME
_UP
_PGUP
_C_MINUS
_LEFT
_C_CENTER
_RIGHT
_C_RIGHT
_C_PLUS
_END
_DOWN
_PGDN
_INS
_DEL
_F11
_F12
_LESS
_EQUALS
_GREATER
_ASTERISK
_R_ALT
_R_CONTROL
_L_ALT
_L_CONTROL
_MENU
_L_WINDOWS
_R_WINDOWS
Program MiSegundoFondo;
Private
int id2;
end
Begin
set_mode(640,480,16);
id2=load_fpg(“prueba.fpg”);
put_screen(id2,1);
Loop
150
if (key(_esc))
break;
end
Frame;
End
Unload_fpg(id2)
end
151
quedara pues una referencia en el ejecutable a ese archivo
inválida. La alternativa es incorporar dentro del propio
fichero EXE (o DLL) los iconos, integrándolos en el código
binario como una bacteria fagocitada por una ameba. De esta
manera, se tiene sólo el archivo ejecutable con todos los
iconos que usará, en su interior.
152
como unload_map o unload_fpg .No es posible cambiar el icono
de una ventana ya inicializada, pero se puede volver a llamar
a set_mode para activar los cambios.
Program ejemplo;
Private
Int iconfpg;
End
Begin
Iconfpg=load_fpg(“iconos.fpg”);
Set_title(“Prueba”);
Set_icon(iconfpg,1);
Set_mode(640 ,480,MODE_16BITS,MODE_MODAL);
Loop
Frame;
end
End
153
IconPackager, Image Icon Converter, IconCoolEditor, Coffe
IconEditor y muchos otros más:
(http://www.popularshareware.com/010-rating-1.html)
PE Resource Explorer:
http://www.wilsonc.demon.co.uk/d7resourceexplorer.htm
154
CAPÍTULO 4:PROCESOS,FUNCIONES,VARIABLES,CONSTANTES
Concepto de proceso:
Principio:
Dibujar tu nave
Dibujar los enemigos
Dibujar los disparos
Chequea r si hay colisiones
Si un disparo toca un enemigo, matarlo
Si un del enemigo te toca: matarte
Chequear si hay algún movimiento de joystick
Calcular la nueva posición acorde al movimiento del joystick
Crear un nuevo disparo si has apretado el botón de disparar
Calcular los movimientos de los enemigos
Ir al Principio.
155
Este ejemplo es muy básico y espero que se entienda.
Hace todo lo necesario desde arriba hasta abajo, y vuelve a
empezar, haciendo lo mismo una y otra vez. Evidentemente, se
necesitan más partes del programa, como concretar qué es lo
que pasa cuando un enemigo o tú muere, pero este ejemplo sólo
es para mostrarte la manera de programar que en Fenix NO se
utiliza.¿Por qué? Porque cuando el videojuego se vuelve cada
vez más complicado,el bucle del programa se hace cada vez
mayor y el código se vuelve pronto desordenado y caótico. La
solución, ya digo, pasa por la multitarea (programación
concurrente). Multitarea significa que más de un programa
(los procesos) están ejecutándose al mismo tiempo. Así, en
vez de un único bucle inmenso como el anterior, donde las
líneas una tras otra se ejecutan para hacer todos los
movimientos, colisiones, chequeos,etc, se tiene más
programas/procesos pequeños que hacen todo esto continuamente
por separado.
156
es muy importante no llegar al momento donde están en mitad
del código pensando por qué no funciona sin tener ni idea…
157
almacenar en consecuencia serán unos determinados. Las
variables predefinidas se utilizan, básicamente, para
controlar los diferentes dispositivos del ordenador
(ratón,teclado, pantalla, tarjeta de sonido,joystick…)y los
gráficos de los juegos.
158
Además de GRAPH y FILE, existen otras dos variables
locales predefinidas más de las que querría decir cuatro
cosas ahora: son la variable X y la variable Y. Éstas tienen
como valor la coordenada x e y respectivamente del centro de
la imagen dada por GRAPH (y FILE en su caso). Es decir,
sirven para establecer las coordenadas del cent ro de la
imagen asociada al proceso donde se definen. Igual que GRAPH,
cada proceso puede dar valores diferentes a X e Y de forma
independiente unos de otros porque cada proceso no interfiere
– a priori- en los demás: es como si cada proceso tuviera una
copia particular de las variables X e Y con sus valores
propios.
Creación de un proceso:
159
quieran, llamar a otros procesos, "matarlos" (hacer que dejen
de ejecutar su código), congelarlos (hacer que paren de
ejecutar su código, pero sin "matarlo"), dormirlos
(congelarlo haciendo que su gráfico no aparezca en pantalla),
interactuar con ellos y cualquier otra cosa que se nos
ocurra. Como hemos dicho, s e puede hacer más de una llamada a
un proceso concreto (ej.: cada vez que el protagonista tenga
que disparar se llama al mismo proceso disparo, ahorrando
muchísimas líneas de código). Y pueden existir infinidad de
procesos a la vez, cada uno ejecutando su código único (lo
que puede ralentizar el ordenador si hay demasiados, pero
nada es perfecto).
PROGRAM ejemplo_procesos;
GLOBAL
INT id1;
END
BEGIN
set_mode(640,480,16,MODE_FULLSCREEN);
id1=load_fpg("imagenes.fpg");
personaje();
END
PROCESS personaje()
BEGIN
x=320; y=240; file=id1; graph=1;
LOOP
160
IF (key(_up)) y=y-10; END
IF (key(_down)) y=y+10; END
IF (key(_left)) x=x-10; END
IF (key(_right)) x= x-10; END
FRAME;
END
END
161
programa Father puede tener todavía líneas por ejecutar –no
en este ejemplo, pero sí es lo normal -, por lo que el
ordenador empezará a ejecutar en paralelo los códigos de
todos los procesos existentes en ese momento.
162
verdadero caos. Por lo tanto, los procesos que terminen antes
su ejecución tendrán que esperarse siempre a los procesos más
lentos para ir todos al mismo compás que marca el FRAME. De
todo esto se puede deducir, por ejemplo, que si escribimos un
bucle infinito sin sentencia FRAME el programa posiblemente
se cuelgo. ¿Por qué? Porque el proceso que tenga ese bucle
infinito nunca llegará al FRAME, y los demás procesos estarán
pausados esperándole sin remedio. O sea que cuidado.
Parámetros de un proceso:
PROGRAM mi_juego;
GLOBAL
INT puntos;
END
LOCAL
INT energia;
END
BEGIN
mi_proceso(1,2,3,4);
163
//Más instrucciones
END
PROGRAM mi_juego;
PRIVATE
id2;
END
BEGIN
id2=mi_proceso(160,100);
//Más instrucciones
164
END
PROCESS mi_proceso(x,y)
PRIVATE
n;
END
BEGIN
set_mode(640,480,16);
graph=load_png("imagen.png");
FROM n=0 TO 99;
x=x+2;
y=y+1;
FRAME;
END
END
165
porción de código fuente de forma que cuando sea necesario
éste, y las veces que sea necesario, no haya que escribirlo
siempre integramente sino que simplemente se llame (se
“invoque”) por su nombre y entonces su código interno
–escrito solamente una vez- se ejecutará.Utilizar funciones
es una forma de trabajar mucho más elegante, eficiente y
fácil de depurar.
Así pues, ¿qué hay que escribir para crear una función?
Las funciones se crean igual que un proceso cualquiera, como
los que hemos creado hasta ahora, pero tienen dos o tres
pequeñas grandes diferencias:
166
nuestra función, escribiremos la palabra FUNCTION
167
Es evidente que, al no ser un proceso, las funciones no
tienen definidas ninguna de las variables globales y locales
accesibles por cualquier proceso: así que GRAPH,X,ANGLE…no
tendrán ningún sentido para una FUNCTION.
program hola;
private
int c;
end
begin
loop
c=mifuncion(rand(2,8));
write_var(0,100,100,4,b);
frame;
end
end
function mifuncion(int a)
private
int b;
end
begin
b=a*a;
return (b);
end
168
el resultado (en este caso, la multiplicación por sí mismo
del valor pasado por parámetro). Fíjate que en el programa
principal se recoge lo que devuelve “mifunción” en una
variable y a partir de aquí se continúa ejecutando el
programa principal.La gracia de este ejemplo es ver que el
programa principal no continúa hasta que “mifunción” haya
acabado (se deja de ejecutar el código del programa principal
para comenzarse a ejecutar el código de la función
“mifunción”, hasta que éste acabe), y que, como siempre, la
sincronizacióncon otros procesos (si el proceso principal se
ha retrasado un pelín respecto los demás) se realizará en su
frame; correspondiente.
program fafafa;
global
int v=2;
int w;
end
begin
p();
end
function p()
begin
v=3;
q();
end
function q()
begin
w=v;
r();
end
169
process r()
begin
write(0,1,1,0,w);
loop
frame;
end
end
*Variables GLOBALES:
170
Sólo se podrán declarar variables globales en el proceso
principal; en ningún otro proceso se pueden declarar –cosa
lógica por otra parte-.Para declarar variables globales, si
son necesarias, simplemente hemos visto que es necesario
escribir antes del bloque BEGIN/END del proceso principal lo
siguiente:
GLOBAL
Declaraciones_y_posibles_inicializaciones;
END
*Variables LOCALES:
171
accederá a un valor numérico u otro. Cada proceso accederá
con la variable local X, a su propia coordenada X.
LOCAL
Declaraciones_y_posibles_inicializaciones;
END
*Variables PRIVADAS:
172
Esto es porque si se declaraba privada, la variable “id1”
solamente tendría sentido dentro del proceso del programa
principal, pero no dentro de los demás procesos, donde “id1”
no significaría nada. Como en el proceso “personaje” se
utiliza la variable “id1”, ya que su valor se le intenta
asignar a FILE, es necesario que “id1” sea reconocida por
ambos procesos. Podría ser pues o una variable local o una
global, pero está claro que ha de ser global porque “id1”
solamente va a contener un valor único para todos los
procesos que existan: el id entificador –único- del fichero
FPG cargado. No tendría ningún sentido que la variable fuera
local.
PRIVATE
Declaraciones_y_posibles_inicializaciones;
END
173
un proceso, en qué posición aparece, cúal es su tamaño, su
ángulo, su plano de profundidad, etc. De hecho, éstos son
valores que pueden indicarse en diferentes variables locales
predefinidas de los procesos; y todas tienen un valor válido
por defecto, por lo que únicamente se tendrán que establecer
aquellos valores que quieran alterarse.Los datos privados se
utilizan básicamente para contadores y variables de soporte.
Constantes:
174
ya está.
CONST
Nombre_Constante= Valor_numérico;
END
175
CAPÍTULO 4bis: TABLAS Y ESTRUCTURAS
Tablas
GLOBAL
Int mitabla1[3]=0,11,22,33;
END
GLOBAL
Int mitabla1[3];
END
176
una de estas posiciones de memoria, se debe indicar el nombre
de la tabla y, entre los corchetes, un valor numérico para
especificar qué posición de la tabla se quiere consultar o
modificar. Por ejemplo, una sentencia para poner el valor 999
en la posición 1 de "mitabla1" (que inicialmente valdría 11)
sería como sigue:
mitabla1[1]=999;
GLOBAL
Int mitabla2[2][4];
END
Estructuras
177
se le denomina registro, y a cada anotación dentro de una
ficha, campo. Igual que las variables, las estructuras pueden
ser globales,locales o privadas. Por ejemplo, en un juego se
podría definir la siguiente estructura global para guardar la
información sobre la posición en pantalla de tres enemigos:
GLOBAL
STRUCT posicion_enemigos[2];
Int coordenada_x;
Int coordenada_y;
END = 10,50,0,0,90,80;
END
posicion_enemigos[1].coordenada_y = 66;
178
crear una estructura global como ésta:
GLOBAL
STRUCT datos_clientes [9];
Byte codigo_interno;
String DNI;
String nombre;
String apellidos;
Int num_articulos_comprados;
Float dinero_que_debe;
END
END
STRUCT movimiento_enemigos[9]
Int x_inicial;
Int y_inicial;
Int x_inicial;
Int y_final;
END
STRUCT a[2]
179
Int b;
Int c[1];
END = 1,2,3,4,5,6,7,8,9;
a[0].b = 1;
a[0].c[0] = 2;
a[0].c[1] = 3;
a[1].b = 4;
a[1].c[0] = 5;
a[1].c[1] = 6;
a[2].b = 7;
a[2].c[0] = 8;
a[2].c[1] = 9;
180
CAPÍTULO 5:TUTORIAL PARA EL JUEGO DEL LABERINTO
(extraído del tutorial de Wakroo,en http://divnet.divsite.net)
Imágenes y transparencias:
181
Esto no es porque sí. En Fénix, para las imágenes de
profundidad de 16 bits –que son las que usaremos siempre- el
color negro absoluto en un trozo de la imagen indica que ese
trozo es como si no existiera:es “transparente”, no se pinta.
Esto quiere decir que no podremos dibujar nunca nada de color
absoluto porque no se verá, pero no te preocupes, eso no es
mucho problema porque luego veremos que podemos pintar lo que
sea de un color casi casi casi negro, donde nuestro ojo no
notará la diferencia pero Fenix sí y lo tratará como un color
normal.
182
todo lo demás, entonces podremos detectar las colisiones del
personaje con las líneas solamente cuando éstas se produzcan,
ya que el resto de la imagen del laberinto no se toma como
tal: no es imagen.
PROGRAM juego_laberinto;
GLOBAL
INT id1;
END
BEGIN
set_mode(640,480,16,MODE_FULLSCREEN);
id1=load_fpg("imagenes.fpg");
personaje();
END
PROCESS personaje()
BEGIN
x=320; y=240; file=id1; graph=1;
LOOP
IF (key(_up)) y=y-10; END
IF (key(_down)) y=y+10; END
IF (key(_left)) x=x-10; END
IF (key(_right)) x=x-10; END
FRAME;
END
END
PROGRAM juego_laberinto;
GLOBAL
INT id1;
183
END
BEGIN
set_mode(640,480,16,MODE_FULLSCREEN);
id1=load_fpg("imagenes.fpg");
personaje();
laberinto();
END
PROCESS laberinto()
BEGIN
x=320; y=240; graph=2;
LOOP
FRAME;
END
END
PROCESS personaje()
BEGIN
x=320; y=240; file=id1; graph=1;
LOOP
IF (key(_up)) y=y-10; END
IF (key(_down)) y=y+10; END
IF (key(_left)) x=x-10; END
IF (key(_right)) x= x-10; END
184
IF (collision(TYPE laberinto)) x=320;y=240; END
FRAME;
END
END
Fíjate que sólo hemos añadido una línea más a las que
había: la línea IF (collision(TYPE laberinto)) x=320;y=240;
END.
185
Como veis es muy simple. La sentencia advance(); hace
avanzar el gráfico el número de pixeles indicado entre
paréntesis en el sentido en el que está mirando el gráfico.
Si el número es negativo lo hace retroceder. ANGLE es una
variable local predefinida, y representa el ángulo que existe
entre una flecha imaginaria apuntando hacia la derecha hasta
la orientación actual donde mira el proceso correspondiente.
Esta variable se mide en milésimas de grado y va en sentido
contrario a las agujas del reloj. Por lo tanto si ponemos
angle=90000; el proceso estará mirando hacia arriba,
ANGLE=180000; mirará hacia la izquierda, ANGLE=270000; mirará
hacia abajo y ANGLE=360000; o ANGLE=0; hacia la derecha.
Tened cuidado con los ángulos, puesto que Fenix, al pasar de
360000 no ajusta el ángulo a 0, sino que sigue hacia adelante.
Lo mismo pasa al bajar de 0. Se podría dibujar el gráfico en
otro sentido y luego ajustarlo, pero lo más sencillo es
pone rlo mirando hacia la derecha y evitar complicaciones.
PROGRAM juego_laberinto;
GLOBAL
INT id1;
END
BEGIN
set_mode(640,480,16,MODE_FULLSCREEN);
id1=load_fpg("imagenes.fpg");
personaje();
laberinto();
LOOP
enemigo();
Frame;
END
END
186
Fíjate: hemos puesto un LOOP en el proceso principal y
dentro una llamada al proceso enemigo();. Además, hemos
escrito –antes no estaba- la sentencia FRAME dentro del bucle
porque recordad que si no aparece una sentencia FRAME en un
proceso con un bucle infinito, el programa se puede colgar
(porque los demás procesos estarán pausados siempre hasta que
el que no tiene FRAME llegue a él, y si está en un bucle
infinito sin FRAME, eso nunca ocurrirá). De esta manera, en
cada frame se creará un enemigo. Pero como por defecto el
programa funciona a 25 frames por segundo (fps, imágenes que
muestra en un segundo; y que recordad que se puede cambiar
con la sentencia set_fps();) se crean demasiados enemigos -
¡25 por segundo!-. Por lo tanto hay que hacer algo para que
aparezcan menos.
PROGRAM juego_laberinto;
GLOBAL
INT id1;
END
BEGIN
set_mode(640,480,16,MODE_FULLSCREEN);
id1=load_fpg("imagenes.fpg");
personaje();
laberinto();
LOOP
IF (rand(1,100)== 1)
enemigo();
END
Frame;
END
END
187
Ahora toca escribir el código del proceso “enemigo()”.
Pero antes, fíjate que llamando tal cual al proceso
“enemigo()” así a secas, todos los enemigos serán iguales. La
manera más práctica –hay otras que se te pueden ocurrir- para
solucionar esto existen los parámetros.
enemigo(rand(1,640),-50,3,r and(50,150),rand(-
10,10),rand(5,10));
188
gráfico 3 (habrá que crear un gráfico y meterlo en el fpg),
con tamaño aleatorio (SIZE es una variable loca l predefinida
que indica el tamaño del gráfico con respecto al original, en
tanto por ciento: 50 quiere decir la mitad y 200 el doble) y
cantidad de movimiento horizontal y vertical aleatorio
(variables estas dos últimas creadas por nosotros: no son
predefinidas). Con estos datos es muy poco probable que se
creen dos enemigos exactamente iguales.
189
del END y terminando el proceso (puesto que después se llega
al E ND del proceso), y por tanto, haciendo desaparecer el
protagonista y no teniendo pues el jugador nada que controlar,
aunque el programa principal seguirá ejecutándose.
Añadiendo explosiones:
190
Y,estaremos dándole los valores de X e Y del proceso
personaje a los valores X e Y del proceso explosión, y por lo
tanto la explosión tendrá lugar en el sitio donde está el
personaje. Justo lo que queríamos. El código de “personaje()”
queda así finalmente:
PROCESS personaje()
BEGIN
x=600; y=240; graph=1;
LOOP
IF (key(_up)) advance(10); END
IF (key(_down)) advance(-10); END
IF (key(_left)) angle=angle+7500; END
IF (key(_right)) angle=angle-7500; END
IF (collision(TYPE laberinto)) x=320; y=240; END
IF (collision(TYPE enemigo)) BREAK; END
FRAME;
END
Explosion(x,y);
END
PROCESS explosion(x,y)
BEGIN
FROM graph=11 TO 20; FRAME; END
END
191
para dejar las imágenes 4-10 para otros usos todavía no
especificados (los códigos de los FPGs no hace falta que sean
correlativos: puede haber “saltos” entre ellos!).
PROCESS explosion(x,y)
BEGIN
FROM graph=11 TO 20; FRAME; END
exit();
END
PROCESS explosion(x,y)
BEGIN
FROM graph=11 TO 20; FRAME; END
personaje();
END
192
Fíjate que lo que hacemos es una nueva llamada, una vez
se ha visto la explosión, al proceso “personaje()”, con lo
que se vuelve a ejecutar el código de ese proceso, que define
las variables X,Y y GRAPH, y los movimientos de nuestro
protagonista. Fíjate también que cuando “explosion()” llama a
“personaje()” para crearlo de nuevo, el proceso “personaje()”
anterior hace tiempo que dejó de existir –y por eso sólo se
verá un personaje cada vez y no más-, ya que justamente
cuando desde “personaje()” se llama a “explosion()”, a
“personaje()” se le acaba el código y muere. Por lo que
digamos que lo que ocurre es que “personaje()” justo antes de
morir crea a “explosion()”, la cual, a su vez, justo antes de
morir, vuelve a crear a “personaje()”, con lo que se
convierte en un bucle infinito de creación/destrucción de los
procesos “personaje()”/”explosion()”.
193
teniendo el código de “disparo()” tal y como está más abajo
el disparo se crearía en (0,0) y con ángulo de inclinación
igual a 0 porque recordad que todas las variables valen 0
hasta que se les asigna otro valor.
PROCESS disparo(x,y,angle)
BEGIN
graph=4;
LOOP
advance(15);
IF (x<-20) BREAK; END
IF (x>660) BREAK; END
IF (y>500) BREAK; END
IF (y<-20) BREAK; END
FRAME;
END
END
194
Para eso podríamos usar el proceso explosion(); , siempre que
no hayamos puesto ninguna sentencia que finalice el programa
-por ejemplo exit();- ya que si no al matar el primer enemigo
se acabaría el juego. Si la hemos puesto tendríamos que
quitarla o escribir otro proceso para las explosiones de los
enemigos. Para la llamada bastaría con ponerla después del
END del LOOP del proceso “enemigo()” y pasarle los valores X,
Y, y SIZE (como hay enemigos de distinto tamaño, también
habrá explosiones de distinto tamaño).
PROCESS disparo(x,y,angle)
BEGIN
graph=4;
advance(15);
LOOP
advance(15);
IF (x<-20) BREAK; END
IF (x>660) BREAK; END
IF (y>500) BREAK; END
IF (y<-20) BREAK; END
FRAME;
END
END
195
Códigos identificadores y señales. ID
IF (id2=collision(TYPE disparo))
signal(id2,s_kill);
BREAK;
END
196
BREAK;
END
FRAME;
END
END
197
S_SLEEP dormirá el proceso, por lo que su código
no se ejecutará ni aparecerá en pantalla, pero el proceso
seguirá existiendo y por tanto se podrá seguir accediendo a
sus variables locales y modificarlas desde otros procesos.
S_FREEZE congelará el proceso, por lo que su código
no se ejecutará pero el gráfico seguirá quieto en pantalla (y
los demás procesos podrán detectar colisiones con su
gráfico), y también se podrá seguir accediendo y modificando
sus variables locales.
S_WAKEUP despertará a los procesos dormidos o
congelados de manera que sigan ejecutando su código donde lo
dejaron.
signal(TYPE disparo,s_kill);
198
que pongamos en signal pero además, con toda su descendencia.
¿Y qué es eso de la descendencia?
199
Como ejemplo de utilización de las señales dirigidas a
toda la descendencia completa de un proceso padre (todo un
árbol), aquí presento el siguiente código. (Para poderlo
visualizar necesitarás crear una imagen PNG llamada
“circulo.png” -que será como su nombre indica, una bola-).
//Author: Wakroo
Program pausa;
Global
Int mapCirculo;
Int idCirculator;
int texto;
Int pausado = 0;
end
Begin
set_title("Ejemplo de Pausa");
set_mode (640,480,16);
mapCirculo = load_png("circulo.png");
idCirculator = circulator();
write(0,0,0,0,"P para pausar; O para quitar la pausa; esc
para salir");
Loop
If (key(_esc)) exit(""); End
If (key(_p) AND NOT pausado)
signal(idCirculator,s_freeze_tree);
texto = write(0,320,240,4,"Pausa");
pausado = 1;
End
Frame;
End
End
Process circulator()
Begin
circulo(50,100);
200
circulo(100,170);
circulo(150,240);
circulo(200,310);
circulo(250,380);
Loop
Frame;
End
End
Process circulo(x,y)
Private
Int direc = 10;
end
Begin
graph = mapCirculo;
Loop
x =x+ direc;
If (x < 50) direc = 10; End
If (x > 590) direc = -10; End
Frame;
End
End
Añadiendo sonido:
201
constantemente a lo largo de un período de tiempo, ya que la
carga del wav en memoria sólo se realiza la primera vez, y a
partir de ésta se puede ir repitiendo la emisión de ese
sonido sin problemas. Ahora lo veremos.
202
play_wav, y como segundo la distancia al jugador (valor entre
0 –volumen original- y 255 –sonido inaudible).Set_distance
filtra el sonido de un canal de sin hacer uso de las
características avanzadas de algunas tarjetas de audio. Si se
desea una emulación algo más sofisticada, es mejor usar
set_position. Set_position cambia el posicionamiento 3D de un
canal, alterando su sonido ; tiene tres parámetros: el número
de canal devuelto por play_wav,el ángulo respecto al jugador,
en grados (0-360) y la distancia al jugador (0-255). Esta
función filtra el sonido de un canal de una forma algo lenta,
Si sólo se desea una emulación más simple, es más rápido
llamar a set_distance. Y por último, también merece la pena
mencionar a la función set_panning, la cual cambia el balance
de un canal,cambiando el posicionamiento stereo del sonido;
tiene tres parámetros: el número de canal devuelto por
play_wav, el volumen izquierdo (0-255) y el volumen derecho
(0-255).Si se desea dejar el sonido tal cual estaba en un
principio, basta con hacer un SET_PANNING(255,255).
sonido=load_wav("c:\mijuego \efectos\sonido.wav”);
play_wav(sonido,0);
203
Si quisiéramos controlar el volumen con el que suena
este sonido, deberíamos escribir después de play_wav la
función set_wav_volume, la cual tiene dos parámetros: el
canal del sonido devuelto por play_wav y el volumen, que
puede valer entre 0 (silencio) y 128 (sonido original del
efecto). Al volumen en vez de un valor concreto se le podría
poner una variable, de manera que si todos los sonidos tienen
esa variable se podrá controlar el volumen general del juego
alterando el valor de esa variable.
Añadiendo música:
204
la cual no tiene ningún parámetro (aunque hay que escribir
los paréntesis igualmente).
205
digitalizado), así que es un formato intermedio a los midi y
a los wav, tanto en calidad como en tamaño: suenan más
realista que los .mid, pero ocupan más.
206
CAPÍTULO 6:TUTORIAL PARA UN MATAMARCIANOS
(extraído del tutorial de Drumpi,en http://drumpi.esp.st)
Punto de partida:
207
absoluto entre los colores de tu nave debes tenerlo en otra
parte de la paleta. En principio, de todas maneras, no
trabajaremos con paletas de 8 bits en este manual.
Program ejemplo;
Global
Int Graficos;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Loop
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
End
Process nave ()
Begin
Graph=2; /*No es necesario utilizar la variable FILE
porque sólo hemos cargado un FPG*/
X=320;
Y=435; //La nave está a 10 píxeles del extremo inferior
de la ventana
Loop
If (key(_left)) x=x-4; end
If (key(_right)) x=x+4; end
If (x>600) x=600; end //Para que la nave no se
salga por la derecha
If (x<40) x=40; end //Para que la nave no se salga
por la izquierda
Frame;
End
end
208
justo antes del end final del programa principal (después del
unload_fpg(graficos)), así:
Let_me_alone();
Process disparo ()
Begin
Graph=3;
Loop
y=y-15;
frame;
end
end
209
Puedes ver que a cada frame, la posición vertical
decrece en 15 píxeles: en cada frame el disparo va subiendo
por la pantalla 15 píxeles, pues. La posición vertical del
disparo al inicio la conocemos, siemp re será y=435, ya que
nuestra nave nunca se va a mover hacia arriba ni hacia abajo,
pero ¿y la coordenada x? Depende de la posición de la nave,
por lo que usaremos el mismo truco que en el capítulo
anterior: los parámetros. Por ahora vamos a dejar esto en
suspense, vamos a decirle primero al programa cuando tiene
que crear un disparo.
Process nave ()
Begin
Graph=2;
X=320;
Y=435;
Loop
If (key(_left)) x=x-4; end
If (key(_right)) x=x+4; end
If (x>600) x=600; end
If (x<40) x=40; end
If (key(_x)) disparo(); end
Frame;
end
end
210
jerarquía es importante porque tenemos que pensar en que el
programa es como un árbol familiar, y cada nodo del árbol
manda señales al resto. Pero aun no vamos a entrar en eso.
Como hemos visto ya, cada proceso tiene un numero de
identificación, que se puede guardar como hicimos con el FPG,
pero no nos va a hacer falta gracias a otra variable local:
FATHER guarda de manera automática el número de
identificación del padre, y SON del hijo; gracias a estas
variables podemos acceder a las variables locales de estos
procesos.
Process disparo ()
Begin
Graph=3;
Y=435;
X=father.x;
Loop
y=y-15;
frame;
end
end
211
del laberinto. Modificamos el proceso “disparo()” de la
siguiente manera:
Process disparo ()
Begin
Graph=3;
Y=410;
X=father.x;
Z=1;
Loop
y=y-15;
frame;
end
end
212
injugable ¿Qué está pasando? La respuesta es bien sencilla,
el disparo, aunque salga de pantalla aun sigue activo, y
sigue avanzando hasta que llegue al infinito, y claro, si el
primer disparo sigue ejecutándose tras el 100º disparo
significa que el ordenador está moviendo 100 procesos, y
llega un momento en que tiene que ejecutar demasiados
procesos y no le da tiempo. Es nuestro trabajo evitarlo,
tenemos que hacer que el disparo se autodestruya al salir del
área visible. Hay dos formas de hacerlo, una es usando la
función “out_of_region()”, de la que encontrarás más
información en las ayudas, y la otra es la que vamos a usar,
que es bastante más sencilla, que simplemente es comprobar si
está por encima de la posición vertical –15 y salir del
bucle, lo haremos añadiendo un IF así:
Process disparo ()
Begin
Graph=3;
Y=410;
X=father.x;
Z=1;
Loop
y=y-15;
if (y< -15) break; end
frame;
end
end
Process disparo ()
Begin
Graph=3;
Y=410;
X=father.x;
Z=1;
Repeat
y=y-15;
frame;
213
until (y < -15)
end
Program ejemplo;
Global
Int Graficos;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Loop
Enemigo();
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
214
Let_me_alone();
End
Process enemigo ()
Begin
Graph=4;
Y=-40;
Repeat
Y=y+5;
Frame;
Until (y > 520)
end
Program ejemplo;
Global
Int Graficos;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
215
Loop
Enemigo(320); //Indico un valor concreto del
parámetro
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
Let_me_alone();
End
Enemigo(rand(0,640));
Program ejemplo;
Global
Int Graficos;
216
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Loop
If(rand(0,100)<20)
Enemigo(rand(0,640));
End
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
Let_me_alone();
End
enemigo(rand(0,640), rand(-5,5),rand(4,7));
217
X=X+inc_x;
Y=Y+inc_y;
Frame;
Until (y > 520)
end
218
Until (y > 520)
end
219
De esta forma, cuando choque con un disparo, se saldrá del
bucle y desaparecerá de pantalla porque collision devolverá
un valor “true” (verdadero) . Date cuenta de un detalle
importante, y es que después de “disparo” no hemos puesto los
paréntesis, porque si los ponemos le estamos diciendo al
ordenador que cree un proceso disparo (y nos pedirá los
parámetros). Al poner OR en medio de ambas posibilidades
dentro del “Until”, se debe cumplir al menos una de ellas
para salir, pues recuerda el funcionamiento de la operación
OR; si hubiéramos querido que se saliera cuando ambas fueran
verdaderas hubiéramos usado AND.
220
Process enemigo ( x,int inc_x,int inc_y)
Private
Int ID_disparo_acertado;
end
Begin
Graph=4;
Size=rand(65,115);
Y=-40;
Repeat
X=X+inc_x;
Y=Y+inc_y;
ID_Disparo_acertado=collision(type disparo);
If (disparo_acertado)
Signal(ID_disparo_acertado,s_kill);
End
Frame;
Until (y > 520 or ID_disparo_acertado)
end
221
posible, y esto incluye evitar repetir las operaciones o usar
sólo la memoria imprescindible. Esto se consigu e usando el
menor número de variables del tipo más adecuado, usando
procesos sin gráficos para ejecutar operaciones que se
realizan a menudo (normalmente a estos procesos que “no se
ven” se les llama funciones o sub-rutinas), evitando repetir
operaciones que se pueden resolver con bucles, y siendo ya
muy tiquismiquis, evitando acceder a memoria, sobre todo
secundaria (disco duro, disquetes, CD ROM) ya que es lo que
consume más tiempo. Del otro lado tenemos el orden del
código, si usas una variable para siete cosas distintas al
final no sabrás lo que está haciendo en cada momento, por
ejemplo, usar “var1” como contador, como variable auxiliar,
para ver la energía…: reduce pero con moderación. Por ahora
tampoco debe preocuparte demasiado, pero sí debes tener en
cuenta que la carga de archivos (imágenes, música...) tarda
mucho tiempo y consume memoria, por lo que deberías cargar
siempre sólo las imágenes que vayas a usar en cada momento
(de aquí la importancia de saber agrupar las imágenes en los
ficheros).
222
Signal(ID_disparo_acertado,s_kill);
Break;
End
Frame;
Until (y > 520)
end
Program ejemplo;
Const
Int Retardo=10;
End
Global
Int Graficos;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Loop
If(rand(0,100)<20)
Enemigo(rand(0,640));
End
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
End
223
Y a continuación vamos a hacer los cambios en el proceso
nave, que es el que se encarga de crear los disparos:
Process nave ()
Private
Int cont=0;
End
Begin
Graph=2;
X=320;
Y=435;
Loop
Cont=cont-1;
If(cont<0) cont=0; end
If (key(_left)) x=x-4; end
If (key(_right)) x=x+4; end
If (x>600) x=600; end
If (x<40) x=40; end
If (key(_x) AND cont==0 )
Cont=retardo;
disparo();
end
Frame;
end
end
Añadiendo explosiones:
224
Por supuesto, nuestro primer problema es conseguir los
gráficos de una explosión para después meterlo en el FPG. Hay
varias maneras de hacerlo, la primera es la más obvia, y es
que cojas tu editor y te dediques a pintar el fuego
expandiéndose y desapareciendo, algo seguramente difícil de
hacer de forma realista. También podemos buscar una explosión
en las numerosas librerías gratuitas que circulan por la red
(o “ripear” los gráfico de otros juegos, cosa que, además de
no ser legal, quita originalidad y personalidad a tu juego).
Nosotros optaremos por la primera opción.
225
Process enemigo ( x,int inc_x,int inc_y)
Private
Int ID_disparo_acertado;
Int cont;
end
Begi n
Graph=4;
Size=rand(65,115);
Y=-40;
Repeat
X=X+inc_x;
Y=Y+inc_y;
ID_Disparo_acertado=collision(type disparo);
If (disparo_acertado)
Signal(ID_disparo_acertado,s_kill);
Break;
End
Frame;
Until (y > 520)
Cont=5;
For(cont=5;cont<=19;cont++)
Graph=cont;
Frame;
End
end
Poco más hay que explicar, cada frame muestra una imagen
distinta, en orden, dando la impresión de animación.
Ejecútalo y verás que efecto más fantástico. Haz pruebas con
diversas explosiones de distintos tamaños, colores o número
de imágenes hasta que des con lo que buscas.
226
ello necesitaremos una variable “energía”. El tipo de esa
variable viene dada por factores externos, es decir, si otro
proceso va a consultar su energía no podremos declararla como
priv ada; tampoco global, pues necesitamos una por enemigo y
no sabemos a ciencia cierta cuantos va a haber como máximo.
Hacerla variable local serviría en caso de que otro proceso
dependiera de si un enemigo concreto está muerto, o le queda
poca energía,etc, pero recuerda que entonces la variable
energía también estará presente en otros procesos como
disparo, el principal o incluso la nave protagonista, aunque
no la necesiten para nada. Como en nuestro caso la variable
“energía” no va a ser consultada para nada por ningún otro
proceso, ya que nuestros enemigos aparecen, bajan y mueren
sin ningún control, usaremos una variable privada.
227
For(cont=5;cont<=19;cont++)
Graph=cont;
Frame;
End
end
228
Una “región de pantalla” no es más que una “ventana”
invisible. Se trata de una zona invisible de la pantalla que
puede contener una serie de gráficos. De hecho, de forma
predefinida, toda la ventana del juego es ya una región, la
denominada región cero. Fenix nos permite definir hasta
nueve regiones, de la 1 a la 9, del tamaño que nos de la
gana, siempre que no se salgan de la zona de pantalla; la
región cero no se puede definir por razones obvias.
Process energia_nave ()
Begin
X=320;
Y=30;
Graph=6;
Loop
Frame;
End
end
229
Poco hay que decir de esto, ya sabes que lo único que hace es
mostrar un gráfico en una posición determinada. Ahora tenemos
que definir la región, la función clave es define_region y
necesita cinco parámetros: el número de región que vamos a
definir, ya sabes que es un número entre 1 y 9, después hay
que indicar las coordenadas x e y de la esquina superior
izquierda de nuestra ventana de región, y por último el ancho
y el alto respectivamente en pixeles de la ventana.
Definiremos la región 1, en la posición 320-100 horizontal
(mitad de la pantalla menos mitad del gráfico) y 30-10
vertical (posición vertical del gráfico menos la mitad del
alto de este), con un ancho del tamaño “energia_jugador” y un
alto de 20, o sea, el alto del gráfico. Para modificarlo en
cada frame, (para actualizar su ancho a lo que valga
“energia_jugador” ), es necesario volver a definirla, por eso
será lo primero que hagamos al entrar al bucle.
Process energia_nave ()
Begin
X=320;
Y=30;
Graph=6;
Loop
Define_region(1,220,20,energia_jugador,20);
Frame;
End
end
Process energia_nave ()
230
Begin
X=320;
Y=30;
Z=-1;
Graph=6;
Region=1;
Loop
Define_region(1,220,20,energia_jugador,20);
Frame;
End
end
231
por eso lo controlaremos desde las naves enemigas, esta es
una de las ventajas de haber definido “energia_jugador” como
variable global. A escribir:
Process energia_nave ()
Begin
X=320;
Y=30;
232
Z=-1;
Graph=6;
Region=1;
Loop
If (energia_jugador<0)
Energia_jugador=0;
End
Define_region(1,220,20,energia_jugador,20);
Frame;
End
end
Process energia_nave ()
Begin
X=320;
Y=30;
Z=-1;
Graph=6;
Region=1;
Loop
If (energia_jugador<0)
Energia_jugador=0;
233
End
If (energia_jugador>200)
Energia_jugador=200;
End
Define_region(1,220,20,energia_jugador,20);
Frame;
End
end
If(energia_jugador<0)
energia_jugador=0;
write(0,320,240,1,"GAME OVER!!!");
signal(Type nave,s_kill);
Break;
End
234
Aunque hay un detalle que se te puede habe r pasado por
alto: “retardo” no es una variable, es una constante, y por
lo tanto no se puede modificar. Bueno , tan sencillo como
cambiar esa línea a la zona de variables globales y listo. El
proceso nave queda así (hemos añadido el SWITCH):
Process nave ()
Private
Cont=0;
End
Begin
Graph=2;
X=320;
Y=435;
Energia_nave();
Loop
Cont --;
If (cont<0) cont=0; end
If (key(_left)) x=x-4; end
If (key(_right)) x=x+4; end
If (x>600) x=600; end
If (x<40) x=40; end
Switch(energia_jugador)
Case 0..30:
Retardo=10;
End
Default:
Retardo=5;
End
end
If (key(_x) and (cont==0))
Cont=retardo;
disparo();
end
Frame;
end
end
Program ejemplo;
Global
Int Retardo=10;
Int Graficos;
Int energia_jugador=100;
End
235
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Loop
If(rand(0,100)<20)
Enemigo(rand(0,640));
End
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
Let_me_al one();
End
236
Y además, modificaremos el código del proceso nave de la
siguiente manera (hemos añadido el bloque
IF(energia_jugador>195)/ELSE/END):
Process nave ()
Private
Int Cont=0;
End
Begin
Graph=2;
X=320;
Y=435;
Energia_nave();
Loop
Cont --;
If (cont<0) cont=0; end
If (key(_left)) x=x-4; end
If (key(_right)) x=x+4; end
If (x>600) x=600; end
If (x<40) x=40; end
Switch(energia_jugador)
Case 0..30:
Retardo=10;
End
Default:
Retardo=5;
End
end
If (key(_x) and (cont==0))
Cont=retardo;
If(energia_jugador>195)
Disparo(x-10);
Disparo(x+10);
else
disparo(x);
end
end
Frame;
end
end
237
Disparos de los enemigos:
Process edisparo(x,y)
Begin
graph=3; /*El gráfico puede ser otro. Aquí utilizamos el mismo
gráfico que el de los disparos nuestros*/
Z=1;
Repeat
y=y+5;
if(collision(TYPE nave))
energia_jugador=energia_jugador-1;
end
frame;
until(y>480)
FRAME;
END
238
Las fuentes FNT. La aplicación “FNTEdit”:
239
grabamos es la fuente de letra en “abstracto”, no es ningún
texto concreto ni nada.
240
que a partir de una estándar podemos crear una fuente FNT con
FNTEdit).
http://fonts.tom7.com
http://www.free-fonts.com
http://www.goodfonts.org
http://thefreesite.com/Free_Fonts
http://www.1001freefonts.com
http://www.freetype.org
http://www.google.com
241
a poner la condición para que sume un punto: esto último lo
haremos añadiendo la línea puntos=puntos+1; en el proceso
enemigo, como podemos ver a continuación:
242
Antes que nada, hay que hacer lo mismo que con las
imágenes, cargar la fuente, para ello hay distintas
funciones. Tenemos “load_fnt”, el parámetro que hay que
pasarle es la ruta absoluta o relativa –según convenga- del
archivo , igual que las imágenes, y al igual que ellas también
esta función devuelve un número identificador.
Por ejemplo, tenemos un tipo de letra que hemos llamado
"mi_letra.fnt” y hemos declarado una variable global para
almacenar su ID llamada “letra_id”, la línea para cargarlo
sería “letra_id=load_fnt(“mi_letra.fnt”);” siempre y cuando
el archivo de fuente este en la misma carpeta desde donde se
ejecuta el DCB.
Program ejemplo;
Global
Int Retardo=10;
Int Graficos;
Int Puntos=0;
Int energia_jugador=100;
End
243
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Put_screen(graficos,1);
Nave();
Write_var(0,320,10,1,puntos);
Loop
If(rand(0,100)<20)
Enemigo(rand(0,640));
End
If (key(_esc)) break; end
Frame;
End
Unload_fpg(graficos);
Let_me_alone();
End
If (puntos==100)
signal(Type enemigo,s_kill);
write(0,120,240,0,"¡Muy bien! ¡Has sorteado los
asteroides con éxito!. Pulsa ESC para salir");
End
244
Usando el ratón:
245
mismos botones del ratón para disparar. Es tan sencillo como
cambiar un par de líneas:
Process nave ()
Private
Int Cont=0;
End
Begin
Graph=2;
X=320;
Y=435;
Energia_nave();
Loop
Cont=cont-1;
If (cont<0) cont=0; end
X=mouse.x;
Switch(energia_jugador)
Case 0..30:
Retardo=10;
End
Default:
Retardo=5;
End
end
If (mouse.left and (cont==0))
Cont=retardo;
If(energia_jugador>195)
Disparo(x-10);
Disparo(x+10);
else
disparo(x);
end
end
Frame;
end
246
Los usos del ratón son tan variados como tu imaginación
quiera, ahora es cosa tuya seguir investigando y probando.
247
-2n parámetro: Es la librería en donde se encuentra el
dibujo que vamos a usar para el scroll (nos referimos al FPG,
ya sabes que si este no está en un FPG porque lo has cargado
con load_png o cualquier otro formato similar debes indicar
como fichero el número cero).
-3r parámetro: Es el número identificador de la imagen,
bien el número dentro del FPG o el dato devuelto por
funciones de carga de imágenes. Es obligatorio especificar un
número aquí, de otra forma el scroll no se podrá iniciar y
dará error al compilar.
-4º parámetro: Es el fondo. Cada scroll nos permite usar
dos imágenes simultáneamente, dos fondos con distintas
velocidades para darle profundidad al escenario. Éste es el
segundo gráfico que estará después del primero, es decir, “al
fondo”. No es obligatorio especificar un fondo –poniendo un 0
no se especifica-, pero si se pone sí debe estar en la misma
librería FPG que el segundo parámetro, o bien ha de ser un
identificador devuelto por load_png.
-5º parámetro: Es la región en la que va a estar el
scroll. Ya hemos hablado de regiones, esto es útil si divides
la pantalla y quieres que cada parte sea un scroll para un
jugador distinto, o para hacer un pequeño mapa scrollable a
modo de zona pequeña dentro de la ventana principal. Si vas a
usar toda la pantalla, ya sabes que el número que tienes que
indicar es el cero.
-6º parámetro:El último es el parámetro “flags”. En
general, por “flag”(bandera en inglés) se entiende aquel
parámetro –o variable según el caso- que dependiendo del
valor que tenga,modifica la configuración de algo de una
manera o de otra: es como un cajón de sastre donde cada valor
que puede tener ese parámetro o variable “flag” cambia un
aspecto diferente de aquello a lo que afecta. Por ejemplo, en
este caso, poniendo un 0 como valor del 6º parámetro de
start_scroll, el scroll se moverá hasta que el margen del
gráfico principal –el 2º parámetro- coincide con uno de los
límites de la región y entonces el scroll se parará. Sin
embargo podemos conseguir que el scroll se repita sumando los
siguientes valores a este parámetro: sumando 1 el gráfico
principal se repite horizontalmente, sumando 2 el gráfico
principal se repite vertica lmente (si ponemos un tres, el
gráfico principal se repite cíclicamente tanto horizontal
como verticalmente), sumando 4 el gráfico de fondo se repite
horizontalmente y sumando 8 el gráfico de fondo se repite
verticalmente. A partir de aquí podemos hacer las
combinaciones que queramos para repetir o no el gráfico
principal y/o el de fondo.
248
Una vez iniciado no hay que olvidarse de cerrar el
scroll usando stop_scroll pasándole el número de scroll que
queremos parar. Así pues, modificamos el programa principal
de la siguiente manera (añadiendo las líneas de start_scroll
y stop_scroll y quitando put_screen):
Program ejemplo;
Global
Int Graficos;
Int energia_jugador=100;
Int Retardo=5;
Int Puntos=0;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Start_scroll(0,graficos,1,0,0,2);
Nave();
Write_var(0,320,10,1,puntos);
Loop
If (rand(0,100)<20)
Enemigo(rand(0,640),rand(-5,5),rand(4,7));
End
If (key(_esc)) break; end
Frame;
End
Stop_scroll(0);
Unload_fpg(graficos);
Let_me_alone();
End
249
Quizás te hayas llevad o una desilusión cuando hayas
visto que el fondo no se ha movido, y es que, vale, lo has
iniciado,(prueba de ello es que el fondo aun está ahí aún
habiendo quitado el put_screen), pero no le has dicho que se
mueva. Para conseguir ese movimiento hay que modificar la
estructura de scroll.
Hay que aclarar antes que como cada scroll tiene dos
planos hay dos variables “x” e “y”, una por cada uno, y por
ello existe “x0” para controlar la coordenada x del primer
plano del scroll y “x1”para el segundo (o el fondo), y lo
mismo para las coordenadas “y”. Así que lo que nos falta por
escribir en el proceso principal es la línea
Scroll[0].y0=scroll[0].y0-2;, justo antes del frame:
Program ejemplo;
Global
Int Graficos;
Int energia_jugador=100;
Int Retardo=5;
Int Puntos=0;
End
Begin
set_mode(640,480,16);
set_fps(60,1);
Graficos=load_fpg(“prueba.fpg”);
Start_scroll(0,graficos,1,0,0,2);
Nave();
Write_var(0,320,10,1,puntos);
Loop
If (rand(0,100)<20)
Enemigo(rand(0,640),rand(-5,5),rand(4,7));
End
If (key(_esc)) break; end
Scroll[0].y0=scroll[0].y0-2;
250
Frame;
End
Stop_scroll(0);
Unload_fpg(graficos);
Let_me_alone();
End
251
CAPITULO 7: TUTORIAL PARA UN RPG BÁSICO
(extraido del tutorial de Michael Sexton,en http://www.div-arena.co.uk)
La pantalla de inicio:
252
para las palabras del menú inicial “Jugar” y “Salir” llamada
“select.fnt”.
Program RPG;
Global
Int file1;
Int select_fnt;
Int title_fnt;
Int level=1;
End
Begi n
set_mode(320,200,16);
set_fps(60,1);
file1=load_fpg(“rpg.fpg”);
select_fnt=load_fnt(“select.fnt”);
title_fnt=load_fnt(“titlescr.fnt”);
Loop
Switch(level)
Case 1:
Loop
frame;
End
End
Case 2:
Loop
frame;
End
End
Case 3:
Loop
frame;
End
End
Case 4:
Loop
frame;
End
End
Case 5:
Loop
frame;
End
End
End
253
frame;
End
End
Case 1:
put_screen(file1,605);
254
write(title_fnt,150,50,4,”Pon el título de tu juego
aquí");
write(select_fnt,150,140,4,"Jugar");
write(select_fnt,150,170,4,"Salir");
Loop
frame;
End
End
Global
Int file1;
Int select_fnt;
Int title_fnt;
Int level=1;
Int selection;
End
Case 1:
put_screen(file1,605);
write(title_fnt,150,50,4,”Pon el título de tu juego
aquí");
write(select_fnt,150,140,4,"Jugar");
write(select_fnt,150,170,4,"Salir");
cursor();
Loop
if(selection==1) level=2; break; end;
if(selection==2) fade_off();exit(“Gracias por
jugar”);end
frame;
255
End
fade_off();
delete_text(0); /*Si no borramos los textos, saldrán en
los otros niveles*/
clear_screen(); /*Si no borramos antes el fondo, cuando
queramos poner otra imagen de fondo
–en el nivel 2 - nos va a dar error*/
let_me_alone();
fade_on();
End
256
PROCESS cursor()
PRIVATE
Int c;
END
BEGIN
file=file1;
graph=5;
x=70;
y=140;
c=3; //Necesaria para el efecto de tembleque
FRAME;
END
257
disponibles jugando con las coordenadas de las variables
locales X e Y, y que éste tenga un efecto de que parezca que
tiembla horizontalmente. Cuando apretemos ENTER se parará y
asignará el valor correspondiente de la opción elegida (1 o
2)a la variable global “selection”, que, como hemos visto,
será usada en el Case 1 para saber, en el momento que cambie
de valor, a qué nivel se va.
Case 2:
Loop
if(key(_esc))
//Generar retardo
while(key(_esc)) frame; end
//Salir
level=1;
break;
end
frame;
End
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
End
258
Así, ahora, cuando estés en el mundo, si apretas ESC la
computadora esperará un momento y entonces volverá a la
pantalla de selección inicial. Pero, ¿por qué se pone ese
WHILE dentro del IF que no hace nada? Pues para que mientras
tengas pulsada la tecla ESC vayan pasando fotogramas
(idénticos entre ellos) indefinidamente, creando así un
estado de “pausa” hasta que dejes de pulsar la tecla ESC.
Éste es un truco que se hace para que tengas la oportunidad
de dejar de apretar la tecla ESC antes de poder hacer
cualquier otra cosa como moverte a otro nivel: la computadora
espera a que dejes de pulsar para continuar trabajando. Si
no, lo que pasaría es que la computadora volvería enseguida a
la pantalla inicial y posiblemente continuarías teniendo
apretada la tecla ESC (el ordenador es más rápido que tu
dedo), con lo que como en el nivel 1 si apretas ESC sales del
juego, de golp e habrías acabado la ejecución del programa sin
poder siquiera pararte en la pantalla inicial de selección.
259
sur –hacia tí- con código 1, el que mira al norte con código
6 y el que mira al este –hacia la derecha- con código 10.
Global
Int file1;
Int select_fnt;
Int title_fnt;
int level=1;
int selection;
int MyChar_Position_X=1516;
int MyChar_Position_Y=1549;
End
Case 2:
MyCharacter(MyChar_Position_X,MyChar_Position_Y);
start_scroll(0,file1,300,0,0,0);
Loop
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End
260
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
End
261
estar en el ce ntro de la pantalla, y lo que se mueva es el
escenario, dando el efecto de que viaje a través del mundo.
Has de darte cuenta, sin embargo, que este tipo de scroll es
diferente del que usamos en el tutorial anterior. Entonces
utilizamos un scroll automático, para el fondo estrellado.
Acuérdate que en aquel caso para hacer un scroll automático
se cambiaban los valores de las coordenadas x0,y0,x1 e y1 del
scroll[0]. Ahora, en nuestro RPG no nos interesa un scroll
automático de nuestro mundo verde, sino un scro ll que se
produzca solamente cuando hagamos que el personaje se mueva,
y además el scroll ha de llevar la dirección adecuada. Aunque
la inicialización con start_scroll es idéntica con ambos
tipos de scroll, es evidente que la forma de tratar
posteriormente el scroll actual es diferente. Esto lo veremos
en seguida.
Loop
if (key(_right) and not key(_up) and not
key(_down))
x=x+2;
if (graph<=10 or graph>=13) graph=10; end;
graph=graph+1;
flags=0;
end
262
x=x-2;
if (graph<=10 or graph>=13) graph=10; end;
graph=graph+1;
flags=1;
end
frame;
End //Loop
End
ctype=c_scroll;
263
¿Cuál es el origen de coordenadas que se va a tomar el
proceso MyCharacter? Pues el extremo superior izquierdo, no
ya de la pantalla, sino del gráfico de primer plano del
scroll que esté iniciado en ese momento. Es decir, en este
caso, el extremo superior izquierdo del cuadrado verde. Y
eso, ¿por qué? Porque será mucho más cómodo para nosotros
programar teniendo en cuenta la posición de nuestro personaje
respecto el mapa que no respecto a la pantalla, porque de
hecho, ya hemos dicho que nuestro personaje va a quedarse
inmóvil en el centro de nuestro juego y lo que irá moviéndose
es el suelo. Por tanto, es mucho más inteligente tomar como
referencia un origen de coordenadas del propio mapa para
saber en qué sitio está nuestro personaje en cada momento, ya
que todos los objetos que pongamos en el mapa (casas,etc)
estarán colocados en unas coordenadas concretas dentro de él,
así que si tomamos las coordenadas de nuestro personaje
referenciadas al mapa, será mucho más fácil comparar
coordenadas entre esos objetos y el protagonista y ver si
éste choca contra una casa o no, por ejemplo.
scroll[0].camera=id;
264
local que contiene eso precisamente, el código identificador
generado automáticamente por Fenix en la creación de ese
proceso concreto.Así que lo que se está diciendo en el
proceso MyCharacter es que el scroll coja como proceso a
“perseguir” a ese mismo, al proceso MyCharacter.
265
convertir nuestro personaje que originariamente mira al este
en nuestro personaje mirando al oeste.
El mapa de durezas
266
tanto, el mapa de durezas es un sistema para indicar que, por
ejemplo, la montaña dibujada en el mapa visible corresponde a
una zona especial en el mapa de durezas y por tanto es
imposible atravesarla. La técnica para conseguir esto es
sencilla: si en un momento determinado el ordenador detecta
que el personaje tiene unas coordenadas X o Y que coinciden
dentro del mapa de durezas con una zona especial (porque
detecta que en ese sitio el mapa de durezas ha cambiado de
color), entonces evita que dichas coordenadas X o Y varien, y
lo que verá el jugador simplemente es que al intentar cruzar
un río, por ejemplo, el protagonista no lo puede pasar.
Seguramente lo entenderás mejor cuando hagamos el código.
267
Es muy importante la elección de un color fijo para el
rellenado de la casa, ya que la idea del uso del mapa de
durezas es comprobar en cada momento si las coordenadas de
nuestro personaje concuerdan con las coordenadas donde en el
mapa de durezas aparece ese color determinado, y reaccionar
en consecuencia. Es decir, que haremos comparaciones de
colores: “si en esta posición del mapa de durezas –donde está
el personaje- está el color X, entonces quiere decir que se
ha encontrado con la casa”. Por lo tanto, hay que tener
perfectamente claro y codificado cuál va a ser el color que
especifique la presencia de los objetos en el mapa, por que
si no las durezas no funcionarán. Entonces, surge el problema
de elegir el color. El color concreto no importa, puedes
poner el que tú quieras, pero tendrá que ser siempre ése.
268
tarjeta de vídeo y del modo gráfico, por tanto, ese código
dependerá de cada ordenador donde se ejecute el juego (de
hecho, el color podrá cambiar ligeramente de ordenador a
ordenador). Así que en principio el valor devuelto por
map_get_pixel no será fijo y esto podría ser un gran problema,
pero veremos ahora cómo se puede solucionar.
269
PROGRAM loquesea;
GLOBAL
int color_pixel;
int grafico_mapa_durezas;
END
BEGIN
set_mode(640,480,16);
grafico_mapa_durezas=load_png("durezas.png");
put_screen(0,grafico_mapa_durezas);
raton();
loop
frame;
end
END
PROCESS raton()
BEGIN
graph=load_png("raton.png");
LOOP
x=mouse.x;
y=mouse.y;
color_pixel =
map_get_pixel(0,grafico_mapa_durezas,x,y);
delete_text(0);
//mostramos el va lor del color que hay debajo del puntero del
raton.
write(0,40,40,3,color_pixel);
frame;
END
END
despega_x
despega_y
obstacle
colorObstaculo
270
Seguidamente, modificaremos el Case 2 del programa
principal de la siguiente manera (los cambios son los
marcados en negrita):
Case 2:
MyCharacter(MyChar_Position_X,MyChar_Position_Y);
start_scroll(0,file1,300,0,0,1);
colorObstaculo =map_get_pixel(file1,102,1500,1500);
Loop
IF(map_get_pixel(file1,102,Son.x+despega_x,Son.y+despega_y)==
colorObstaculo ) obstacle=true;
ELSE
obstacle=false;
END
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
End
271
olvídate de las variables “despega_x” y “despega_y”: ¿qué es
lo que tenemos como 3r y 4º parámetro del map_get_pixel?
Son.X y Son.Y. Acuérdate que Son es una variable predefinida
que referencia al último proceso hijo del proceso actual.
Estamos en el proceso principal, y fíjate que tres líneas
antes, dentro del Case 2, hemos creado el proceso
MyCharacter. Es decir, que Son hará referencia a nuestro
personaje. Y con Son.X y Son.Y estamos accediendo a las
variables locales X e Y del proceso Son, es decir, del
proceso MyCharacter.Por lo que este map_get_pixel lo que hace
es lo dicho: coger el color del píxel que es el centro del
personaje.
272
para que responda a los obstáculos que se encuentre (las
novedades del código están en negrita):
Loop
despega_x=0;
despega_y=0;
273
end
frame;
End //Loop
End
274
personaje choque con la casa, según el Case 2 “obstacle”
valdrá true y por tanto, tal como está en el proceso
MyCharacter no nos podremos mover en ninguna dirección.Sin
embargo, si pulsamos el cursor izquierdo, para “despegarnos”
de la casa, vemos que la comprobación contínua de
map_get_pixel en el Case 2 ahora se realizará con el punto
que está a 2 píxeles a la izquierda del centro. Es evidente
que este nuevo punto no estará chocando a la casa, porque el
que choca es el que está a la derecha del centro. Por lo
tanto, en ese momento “obstacle” volverá a valer false, y
podremos ejecutar los ifs del movimiento otra vez. Y este
sistema es extrapolable a las demás direcciones.¿Y por qué 2
píxeles? Porque hemos hecho que el personaje se mueva de 2
píxeles en 2 píxeles (eso se puede cambiar, claro). Así, la
detección del choque siempre se realizará siempre “un paso”
(un movimiento) antes de efectuarlo realmente. No es la única
solución, pero posiblemente sea la más sencilla de
implementar.
275
-Modifica tu mapa de durezas. Pinta ahora el hueco
que dejaste sin pintar cuando rellenaste de rojo toda la casa
(es decir, el hueco de la puerta de la casa) de otro color
diferente al de la casa, por ejemplo, amarillo. Yo he
dibujado la puerta de tal manera que el punto (1525,1535)
está dentro de ella; si tú no lo tienes igual tendrás que
cambiar estas coordenadas en el código que venga a
continuación. Guárda el nuevo mapa de durezas otra vez dentro
del FPG con el mismo código 102, claro.
Case 2:
…
colorUmbral1=map_get_pixel(file1,102,1525,1535);
Loop
IF(map_get_pixel(file1,102,Son.x+despega_x,Son.y+despega_y)==
colorObstaculo ) …
END
IF
(map_get_pixel(file1,102,Son.x+despega_x,Son.y+despega_y)==co
lorUmbral1) house1=true;
END
276
if(key(_esc))
…
end
if(house1==true)
house1=false;
level=3;
MyChar_Position_X=160;
MyChar_Position_Y=60;
break;
end
frame;
End
…
//clear_screen();Con stop_scroll(0) ya no hace falta esta
líne a
stop_scroll(0);
End
277
Pero fíjate que hemos añadido, (justo antes de ejecutar
otra vez en el Switch el cual volverá a hacer la comprobación
de la variable “level” y entrará pues en el Case 3) una
última orden muy importante: stop_scroll.Esta orden lo que
hace es detener el scroll del mapa verde,pues si no se
parara, al entrar en el nivel 3 se seguiría viendo el mapa
verde porque el scroll –y por tanto, su imagen asociada: el
mundo exterior - se seguiría ejecutando.Se puede entender que
stop_scroll es una función equivalente a clear_screen, sólo
que la primera afecta a los fondos escrolleables que han sido
creados mediante start_scroll, y la segunda a los fondos
estáticos creados con put_screen.
Process MyCharacter_in_House(x,y)
BEGIN
graph=1;
angle=0;
Loop
despega_x=0;
despega_y=0;
278
x=x+2;
if (graph<=10 or graph>=13) graph=10; end;
graph=graph+1;
flags=0;
end
frame;
End //Loop
End
279
A más a más, vamos a crear un nuevo mapa de durezas para
el interior de la casa. Abre el dibujo 401 y pinta el umbral
de la puerta q ue dará otra vez al mundo exterior del mismo
color que tenga la puerta de la casa en el mapa de durezas
que hicimos antes para el mundo exterior –en nuestro caso,el
color amarillo, que viene referenciado por nuestra variable
“colorUmbral1”- , y vuelve a pintar del mismo color que tenga
la casa en el mapa de durezas del mundo exterior,- en nuestro
caso, el color rojo, que viene referenciado por nuestra
variable “colorObstaculo”- todas aquellas partes de la casa
donde no quieras que tu personaje se quiera mov er. Fíjate que
hemos seguido un criterio: en nuestro ejemplo las partes
rojas de todos los mapas de durezas que hagamos serán zonas
prohibidas y las zonas amarillas serán zonas de transición a
otros niveles. Guarda este nuevo mapa de durezas en el FPG
con el código 106.
Case 3:
put_screen(file1,401);
MyCharacter_in_House(MyChar_Position_X,MyChar_Position_Y
);
Loop
IF (map_get_pixel(file1,106,son.x+despega_x,
son.y+despega_y)==colorObstaculo)
obstacle=true;
ELSE
obstacle=false;
END
IF
(map_get_pixel(file1,106,son.x+despega_x,son.y+despega_y)==co
lorUmbral1)
house1=true;
ELSE
house1=false;
END
280
if(house1==true)
house1=false;
level=2;
MyChar_Position_x=1519;
MyChar_Position_y=1546;
break;
end
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
End
281
espada después de que el protagonista haya hablado
previamente con otro carácter y le haya dicho una respuesta
correcta.
Process Wizard(x,y)
Private
Int id2;
Int idangle;
Int angletoChar;
End
Begin
graph=900;
id2=get_id(TYPE MyCharacter_in_House);
Loop
idangle=get_angle(id2);
282
angletoChar=idangle-90000;
frame;
End
End
Y después, Bob:
Process Bob(x,y)
Private
Int id2;
Int idangle;
Int angletoChar;
End
Begin
graph=911;
id2=get_id(TYPE MyCharacter_in_House);
Loop
idangle=get_angle(id2);
angletoChar=idangle-90000;
283
flags=0;
end
if(angletoChar<-135000 and angletoChar>-195000)
graph=911;
end
frame;
End
End
284
Ahora podemos entender un poco más el código de ambos
procesos. Lo que primero hacen es conseguir el identificador
del proceso de tipo MyCharacter_in_House (como sólo hay uno,
no habrá confusión posible) y guardarlo en la variable
privada “id2”.Y con este identificador en el bolsillo, en
cada iteración del Loop se comprobará el ángulo formado por
la línea horizontal y la línea que en ese momento vaya del
centro del gráfico del proceso mago/Bob al centro del gráfico
del proceso MyCharacter_in_House. Después se hace una pequeña
corrección, y el ángulo resultante será el que decida qué
gráfico del mago/Bob (sur,norte,este,oeste) será el que se
visualice en ese momento.
Wizard(250,105);
Bob(130,100);
285
único que tenemos que hacer es almacenar en una variable
global el identificador del proceso MyCharacter_in_House
cuando éste se crea, y utilizar esta variable global en vez
de Son. Es mas, de esta manera incluso no sería necesario
utilizar la función get_id en los procesos mago/Bob porque ya
tendríamos esa variable global que nos serviría. Por tanto,
crea una nueva varible global llamada “idCharHouse” y haz
todos estos cambios que te he comentado. Y por fin,
funcionará.
Case 3:
…
//Recogemos en colorMago el color de la zona del mago
colorMago= map_get_pixel(file1,106,250,105);
//Recogemos en colorBob el color de la zona de Bob
colorBob= map_get_pixel(file1,106,130,100);
Loop
…
IF (map_get_pixel(file1,106, idCharHouse.x+despega_x,
idCharHouse.y+despega_y)==colorMago)
near_wizard=true;
ELSE
near_wizard =false;
END
286
IF(map_get_pixel(file1,106, idCharHouse.x+despega_x,
idCharHouse.y+despega_y)==colorBob)
near_bob=true;
ELSE
near_bob=false;
END
…
End
…
End
Process TalkLines()
Begin
Loop
if(key(_space) and near_wizard==true and
asked_bob_for_key==false and talkwizardlines==1 )
talking=true;
Write(talk_font,160,180,4,"¿Qué quieres? Fuera
de aquí");
//Retardo
frame(5000);
talking=false;
delete_text(0);
end
287
delete_text(0);
MyChar_has_KeyOne=true;
talkwizardlines=2;
end
288
Frame(5000);
talking=false;
delete_text(0);
talkboblines=1;
no=false;
end
frame;
End //Loop
End
289
desaparece. El retardo viene dado por la inclusión del
parámetro numérico en la orden Frame; recuerda que este
parámetro fijaba cuántos fotogramas podían pasar sin que se
mostrara para el proceso en cuestión ningún cambio en
pantalla. En este caso, poniendo un valor como 5000, lo que
estamos haciendo es esperar un tiempo igual a 50 frames para
que el código del proceso pueda continuar ejecutándose
(durante esos 50 frames, evidentemente,los otros procesos que
pudieran existir habrán ejecutado su código de forma
completamente normal). Finalmente, el significado de la
variable “talking” la veremos más adelante (fíjate que se
pone a true antes de mostrar la frase y cambia a false cuando
se ha dejado de mostrar).
MyChar_has_KeyOne=false;
near_bob=false;
near_wizard=false;
talkboblines=1;
talkwizardlines=1;
asked_bob_for_key=false;
talk_font=load_fnt(“talk_fon.fnt”);
290
inicio del Case 3. Justo después de haber creado el proceso
MyCharacter_in_House, Wizard y Bob, seguidamente añade:
talklines();
291
PROCESS talk_cursor()
PRIVATE
Int c;
END
BEGIN
file=file1;
graph=14;
x=70;
y=170;
c=3;
yes=false;
no=false;
x=x+c;
IF (x>=80) c=-3; END
IF (x<=70) c=3; END
FRAME;
IF (key(_esc))
no=true;
y=190;
END
END
SWITCH (y);
CASE 170:
yes=true;
END
CASE 190:
no=true;
END
END
END
292
Y por último, explico el significado de la variable
“talking”. Es buena idea hacer que nuestro personaje no se
pueda mover mientras está conversando con los personajes. Así
que el valor que contiene esta variable denota si en ese
momento el personaje está hablando o no, y por tanto,
podríamos utilizarla para inmovilizar al personaje en
consecuencia. Lo único que tenemos que hacer para eso es
modificar el proceso MyCharacter_in_House forzando a que los
movimientos los haga (a más a más de las restricciones
anteriores) siempre que “talking” valga false. Es decir,
tienes que modificar lo siguiente:
Process MyCharacter_in_House(x,y)
…
End
293
Entrar y salir de la segunda casa:
294
-Crea un nuevo mapa de durezas para la segunda
casa. Si usamos el mismo gráfico 401 para visualizar el
interior de la casa, el mapa de durezas nuevo para esta
segunda casa tendrá que tener la misma disposición espacial
para los obstáculos, pintados en rojo.Lo que ha de cambiar
respecto el mapa de durezas de la primera casa es el color
con el que se denotará el umbral de la puerta de salida hacia
el mundo, por el mismo motivo de antes. Si mantuviéramos el
amarillo, el personaje saldría de la segunda casa pensando
que sale de la primera y aparecería en el mundo verde al lado
de ésta. Por tanto,hagamos que el color del umbral sea ahora
otro. Mejor que sea el mismo que hemos utilizado en el mapa
de durezas del mundo para indicar el umbral de entrada; o
sea, naranja, y así podremos continuar utilizando la variable
“color5”.Guarda este nuevo mapa de durezas con el código 103.
295
Fíjate en la condición que se pone: el personaje ha de
tener la llave para poder entrar.Y fíjate también lo que te
he dicho antes: si hubiéramos dejado “colorUmbral1” como
color del umbral de la casa, inmediatamente antes de este if
que acabamos de poner está el if que comprueba si el
personaje ha chocado o no con el umbral de la primera casa:si
hubiéramos puesto el mismo color de umbral, el personaje no
podría distinguir en qué casa entra.
if(house2==tru e)
house2=false;
level=4;
MyChar_Position_X=160;
MyChar_Position_Y=60;
break;
end
Case 4:
put_screen(file1,401);
idCharHouse=MyCharacter_in_House(MyChar_Position_X,MyChar_Pos
ition_Y);
Loop
IF(map_get_pixel(file1,103,idCharHouse.x+despega_x,
idCharHouse.y+despega_y)==colorObstaculo)
obstacle=true;
ELSE
obstacle=false;
END
IF(map_get_pixel(file1,103,idCharHouse.x+despega_x,
idCharHouse.y+despega_y)==colorUmbral2)
house2=true;
ELSE
house2=false;
296
END
if(house2==true)
house2=false;
level=2;
MyChar_Position_X=300;
MyChar_Position_Y=300;
break;
end
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End //Loop
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
End
297
Un comentario final: en este tutorial ya no volveremos a
retocar más el mapa de durezas, así que los cinco colores que
tiene son los que tendrá. Aprovecho ahora, pues, para
comentarte que sería una buena idea recopilar las cinco
líneas esparcidas por todo el código fuente donde definíamos
el color de cada una de estas partes (las líneas del tipo
colorX=map_get _pixel(…)) y escribirlas juntas una detrás de
otra, al inicio del programa principal, antes del bloque
Loop/End. De esta manera, mantendrás una coherencia en el
código porque sabrás que todas las asignaciones de colores
para las distintas zonas del mapa de durezas se realizan al
principio del juego, en el mismo sitio del código; y justo
después de haber cargado el FPG es el lugar ideal. El código
mejorará en claridad y comodidad. Y de hecho, incluso podrías
hacer otra cosa más, que es crear un nuevo gráfico que
sirviera simplemente como tabla de muestras de colores, y que
a la hora de asignar los valores a la variables “colorX” se
cogieran de allí en vez del mapa de durezas. Con esto
ganarías facilidad a la hora de cambiar colores y/o
posiciones de durezas dentro del mapa.
298
Vamos a hacer que nuestro personaje desde el principio
del juego tiene la espada ya en su inventario. Y vamos a
hacer que para que la pueda usar, el jugador tenga que forzar
la aparición de la barra del inventario y seleccionar la
espada de allí. Haciendo esto, el gráfico de nuestro
persona je cambiará en el mundo y se le verá manejando la
espada, y el inventario en ese momento tendrá que aparecer
vacío. A partir de entonces, si se vuelve a apretar “I”, se
podrá seguir jugando con la espada “desenfundada”. Cuando el
jugador desee, pulsará “I” otra vez y podrá devolver la
espada al inventario haciendo que el protagonista “enfunde”
la espada.
299
-Crea cinco imágenes de nuestro protagonista
mirando hacia el sur (20x20 también), empuñando la espada. En
cada imagen la orientación de la espada ha de ser diferente,
dando la sensación en conjunto de estar moviendo la espada de
derecha a izquierda y viceversa. Guarda estas imágenes con
los códigos de 700 a 704.
to_item_select_screen
last_level
direction
kill_mode
sword_selected
Case 2:
if (to_item_select_screen==false)
idChar=MyCharacter(MyChar_Position_X,MyChar_Position_Y);
start_scroll(0,file1,300,0,0,0);
300
color=map_get_pixel(file1,102,1500,1500);
colorUmbral1=map_get_pixel(file1,102,1525,1535);
color5=map_get_pixel(file1,102,125,55);
end
to_item_select_screen=false;
Loop
…
if(house2==true)
house2=false;
level=4;
MyChar_Position_X=160;
MyChar_Position_Y=60;
break;
end
if (key(_i))
level=5;
break;
end
frame;
End
if (level<>5)
fade_off();
delete_text(0);
let_me_alone();
stop_scroll(0);
fade_on();
end
last_level=2;
End
Case 3:
if (to_item_select_screen==false)
put_screen(file1,401);
idCharHouse=MyCharacter_in_House(MyChar_Position_X,MyCha
r_Position_Y);
Wizard(250,105);
Bob(130,100);
301
talklines();
colorMago= map_get_pixel(file1,106,250,105);
colorBob= map_get_pixel(file1,106,130,100);
end
to_item_select_screen=false;
Loop
…
if(house1==true)
house1=false;
level=2;
MyChar_Position_x=1516;
MyChar_Position_y=1549;
break;
end
if (key(_i))
level=5;
break;
end
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End
if (level<>5)
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
end
last_level=3;
End
Y en el Case 4, igual:
302
Case 4:
if (to_item_select_screen==false)
put_screen(file1,401);
idCharHouse=MyCharacter_in_House(MyChar_Position_X,MyCha
r_Position_Y);
end
to_item_select_screen=false;
Loop
…
if(house2==true)
house2=false;
level=2;
MyChar_Position_X=170;
MyChar_Position_Y=100;
break;
end
if (key(_i))
level=5;
break;
end
if(key(_esc))
while(key(_esc)) frame; end
level=1;
break;
end
frame;
End //Loop
if (level<>5)
fade_off();
delete_text(0);
clear_screen();
let_me_alone();
fade_on();
end
last_level=4;
End
303
Antes de explicar los cambios que hemos introducido en
los tres Case anteriores, que son muy sutiles e importantes,
voy a escribir a continuación cómo ha de ser el código del
Case 5, para que puedas ver de una sola vez todas las partes
del código de los diferentes Cases que están relacionadas
entre sí. Así que el Case 5 ha de ser así:
Case 5:
delete_text(0);/*Si en la pantalla queda algún diálogo
visible,que se borre*/
to_item_select_screen=true;
item_select_screen_background();
signal(idChar,s_freeze);
signal(idCharHouse, s_freeze);
if (sword_in_inventory==true)
item_select_cursor();
else
item_select_cursor_empty();
end
frame(2000);
Loop
if (key(_i))
level=last_level;
break;
end
frame;
End
signal(type item_select_screen_background,s_kill);
signal(type item_select_cursor,s_kill);
signal(type item_select_cursor_empty,s_kill);
signal(idChar,s_wakeup);
signal(idCharHouse,s_wakeup);
frame(2000);
End
304
posibilidad dentro del Loop –para que se vaya comprobando
constantemente - de
que se pulse la tecla “I” para salir del bucle e ir al Case
5.
305
personaje_en_casa, mago,bob…).Si no se ha entrado en el
inventario, se crean normalmente, pero si hemos entrado, no
se crearán.¿Por qué? ¡Porque ya están creados¡ Piensa un
poco. Cuando estemos en la ventana del inventario, los
procesos que estuvieran en ese momento visible continuarán
estándolo,tal como hemos dicho. Así pues, cuando salgamos del
Case 5 porque hemos “cerrado” la pantalla del inventario y
queramos seguir jugando allí donde estábamos, lo que hará
nuestro programa es volver al Case adecuado (2,3 o 4) y
volver a ejecutar su interior. Si no pusiéramos este if, por
tanto, se volverían a crear otra vez los procesos que no han
sido destruidos, por lo que tendríamos dos personajes, o dos
personajes_en_casa, dos magos,etc… Y así, cada vez que
entráramos en el inventario, crearíamos nuevos procesos.
Prueba de quitar el if y verás lo que te comento más
claramente.
306
Está claro: si entramos en un nivel, no nos interesa para
nada tener los procesos de otro nivel funcionando: si estamos
en el mundo verde no queremos para nada el mago, ¿verdad? Y
además, haciendo esto nos facilitamos las cosas porque como
habremos destruido al salir los procesos, al regresar a ese
nivel los volvemos a crear y ya está. De hecho, podrías
probar el quitar esta línea y verás que cada vez que volvamos
a un nivel donde ya hubiéramos estado antes nos aparecería un
nuevo personaje, o personaje_en_casa, o mago,etc…porque no
habríamos matado el anterior.
307
te fijas, eso es lo que he hecho en el Case 5: congelar los
dos procesos, y luego, antes de salir del Case 5,
despertarlos otra vez.
Process Item_Select_Screen_Background()
begin
graph=216;
X=160; //Fondo centrado
z=-5;
loop
frame;
end
end
308
Seguidamente nos encontramos con un bloque if/else que
comprueba si el valor de la variable “sword_in_inventory” es
true o no. Si lo es, se ejecutará otro proceso creado por
nosotros, “item_select_cursor()”, y si no lo es, se ejecutará
el “item_select_cursor_empty()”. Acuérdate de que la variable
“sword_in_inventory” la inicializamos a true por defecto.
Esta variable indicará si la espada está (true) o no (false)
en el inventario. Es decir, que si vale true, deberá aparecer
en la barra del inventario un gráfico indicando que la espada
está disponible para poder cogerla, y si vale false, la barra
del inventario aparecerá vacía, y –esto lo veremos en
seguida - nuestro protagonista deberá aparecer empuñando la
espada. Como puedes ver, hemos dicho a la computadora que
tienes ya la espada de entrada , pero puedes poner esto a
falso, y entonces hacer algo en el juego para que sea
verdadero (en otras palabras, puedes conseguir la espada en
una tienda o similar).
Process Item_select_cursor()
Begin
graph=218;
x=10;
y=10;
z=-15;
loop
if(key(_enter) and graph=218)
graph=221;
309
sword_selected=true;
frame(1000);
end
if(key(_enter) and graph=221)
graph=218;
sword_selected=false;
frame(1000);
end
frame;
end
End
Process Item_select_cursor_empty()
Begin
graph=217;
x=10;
y=10;
z=-15;
loop
frame;
end
end
310
Vamos a hacer que cuando seleccionemos con la tecla
ENTER en el inventario la espada, y salgamos de la pantalla
del inventario, dentro del mundo o de las casas el
protagonista pueda mover la espada cuando se pulse por
ejemplo la tecla “e”. Habrá que modificar dos procesos:
“MyCharacter()” y “MyCharacter_in_House()”, de esta manera:
repeat
graph=graph+1;
frame;
until (graph==709)
graph=6;
else
kill_mode=false;
end
repeat
graph=graph+1;
frame;
until (graph==704)
311
graph=1;
else
kill_mode=false;
end
frame;
End //Loop
End
to_item_select_screen=false;
last_level=0;
sword_in_inventory=true;
kill_mode=false;
sword_selected=false;
If(sword_selected==true)
Sword_in_inventory=false;
End
312
en el Case 5, justo antes de las señales S_KILL. No obstante,
queda pendiente un hecho:si en este momento nuestro personaje
empuña la espada,¿cómo podemos devolver ésta al inventario
otra vez? Queda como ejercicio.
level
MyChar_Position_X
MyChar_Position_Y
MyChar_has_KeyOne
talkboblines
talkwizardlines
asked_bob_for_key
sword_selected
sword_in_inventory
Int savedata[20];
Case 1:
…
313
Loop
if(selection==1) level=2; break; end;
if(selection==2) fade_off();exit("Gracias por
jugar");end
save("player.dat",savedata);
end
frame;
end
…
End
314
ningún sentido. En nuestro juego haremos que se carguen los
valores pulsando CTRL+L, así que, en el Case 1, justo después
del bloque if(key(_control) and key(_s))/end –antes del
Frame;- añadiremos lo siguiente:
load("player.dat", savedata);
level=savedata[0];
MyChar_Position_X=savedata[1];
MyChar_Position_Y=savedata[2];
MyChar_has_KeyOne=savedata[3];
talkboblines=savedata[4];
talkwizardlines=savedata[5];
asked_bob_for_key=savedata[6];
sword_selected=savedata[7];
sword_in_inventory=savedata[8];
break;
end
315
Añadiendo enemigos, medida de vida y Game Over
Process Enemy_Pointer(x,y)
private
id2; //Esta variable contendrá el código
identificador del protagonista
idangle; //Ésta contendrá el ángulo entre el enemigo y
el protagonista
iddist; //Y ésta lo mismo que la anterior pero para la
distancia
hurt=3; //Vida del enemigo
counter_kill=16;//Contador que retrasa la pérdida de
vida del enemigo
move_x;
move_y;
316
end
begin
z=1;
move_x=get_distx(idangle,iddist);
move_y=get_disty(idangle,iddist);
317
if ((move_x<-20 and move_x>-100) and (move_y>-100
and move_y<100))
x=x-1;
end
if ((move_x>20 and move_x<100) and (move_y>-100 and
move_y<100))
x=x+1;
end
if ((move_y<-20 and move_y>-100) and (move_x>-100
and move_x<100))
y=y-1;
end
if ((move_y>20 and move_y<100) and (move_x>-100 and
move_x<100))
y=y+1;
end
318
(Gráfico que ilusta lo explicado sobre las funciones get_distx y get_disty)
Process Health_Meter(x,y)
Begin
loop
If (health==14) graph=200;end
If (health==13) graph=201;end
If (health==12) graph=202;end
If (health==11) graph=203;end
If (health==10) graph=204;end
If (health==9) graph=205;end
If (health==8) graph=206;end
If (health==7) graph=207;end
If (health==6) graph=208;end
If (health==5) graph=209;end
If (health==4) graph=210;end
If (health==3) graph=211;end
If (health==2) graph=212;end
If (health==1) graph=213;end
If (health<=0) graph=214;end
frame;
end
end
319
Como puedes comprobar, utilizo una variable llamada
“health” que controla este proceso, por lo que habrá que
añadirla tambi én a la lista de variables globales enteras del
programa.
Health_Meter(40,170);
atraso=atraso+1;
if (atraso==50)
atraso=0;
end
if (collision(type Enemy_Po inter) and atraso==0)
health=health-1;
end
320
Una nueva variable ha sido usada, “atraso”, así que
tendrás que declararla en la lista de variables globales
enteras. Fíjate que lo que hemos hecho ha sido simplemente
hacer que cada vez que ocurra una colisión con un proceso de
tipo enemigo,nuestro protagonista disminuya en 1 su energía.
¿Y qué pinta la variable “atraso”? Bueno, es una variable que
si te fijas sirve para que cuando un enemigo choque con el
protagonista no le quite en un santiamén la energía que
tiene,porque normalmente los choques no son instantáneos,
sino que el choque se produce durante bastantes frames
seguidos, por lo que si en cada frame se le quitara un punto
de vida, por cada leve roce con un enemigo nuestro
protagonista moriría. El truco que se ha hecho servir es
hacer que solamente una vez de cada 50 –cuando “atraso” valga
0,y eso ocurrirá cada 50 frames- se produzca la disminución
de energía. Esto querrá decir que en cada choque, la
disminución de energía será más pausada y el jugador tendrá
más posibilidades de reaccionar y salir del choque sin perder
tanta energía.
health=14;
If (Health==0)
write (select_fnt,150,100,4,"Game Over:");
let_me_alone();
fade(100,0,0,5);
level=1;
frame(5000);
break;
end
321
cuatro parámetros: los tres primeros representan un
porcentaje de las componentes primarias del color: R, G y B.
Un porcentaje de 100% dejará un color sin modificar,
mientras un porcentaje de 0% eliminará por completo la
componente.También es posible especificar un valor por encima
de 100. Se considera que un valor de 200 en una componente,
debe poner al máximo la intensidad de la misma. Estos valores
permiten hacer co mbinaciones flexibles, como por ejemplo un
fundido al blanco (200,200,200) o bien aumentar la intensidad
de color de una o varias componentes (150,150,100). Tal vez
el valor más habitual sea el fundido al negro (0,0,0) o
restaurar el estado normal de los colores (100,100,100).El
cuarto parámetro es la velocidad: el valor de velocidad es un
valor entero entre 1 y 64. Un valor de 64 activaría los
porcentajes de color indicados de forma inmediata en el
próximo frame, mientras un valor de 1 iría modificando las
componentes de forma sucesiva, de forma que alcanzarían los
valores especificados como parámetro en aproximadamente unos
60 frames. Un valor intermedio permite realizar la transición
más rápidamente. Hay que tener en cuenta que en modo 16 bits
esta función modifica los valores de todos los pixels en
pantalla, realizando una costosa operación por cada uno de
ellos. Esta operación es muy lenta y representa un coste
considerable en la tasa de frames por segundo. Si sedesea
hacer un juego con efectos de colorización globales, se
recomienda usar los efectos localmente en una zona reducida
de la pantalla. Si ello no fuera posible, entonces es
preferible escoger una resolución de pantalla limitada para
aumentar el rendimiento. Hay que tener en cuenta que, incluso
una vez acabada la transición, si las componentes finales de
loscolores no son 100 para las tres, el motor gráfico seguirá
modificando los pixels de pantalla cada frame, por lo que el
perjuicio al rendimiento no se limitará al período que dure
la transición y seguirá presente una vez acabada ésta.
322
¡Y ya está¡¿Ya está? ¡Pero los enemigos no salen por ningún
lado!Claro, los tenemos que crear. Modifica el Case 2 –el
mapa verde- para que tus enemigos se muestren. Yo he creado
tres y los he puesto en el centro del mundo, así que tendrás
que ir a buscarlos si te quieres encontrar con ellos. Así
pues, justo después de crear nuestro protagonista llamando al
proceso “MyCharacter()” –dentro del bloque
if(to_item_select_screen)/end, crearemos tres enemigos, así:
enem y_pointer(1600,1400);
enemy_pointer(1750,1300);
enemy_pointer(1200,1750);
323
cargarlos según fuera necesario. La idea es buena, pero el
problema con los mapas grandes los seguiríamos teniendo, y
además, en el momento de cargarlos –en mitad del juego- se
notaría más el “parón”.
324
En el último capítulo de este manual aparece un apartado
donde se muestra y explica un código fuente de ejemplo para
la creación de un mapa tileado muy básico. No se ha incluido
aquí porque hace uso de funciones y conceptos que se
introducirán en páginas posteriores a la actual.
Includes
325
Esta palabra ha de escribirse siempre just o después de
la primera línea del programa (la línea program…)y antes del
begin del proceso principal.Simplemente hay que poner
Include “nombre_del_archivo_con_codigo_a_reutilizar.inc”;
326
tal manera que el código de ésta sólo hubiera que escribirlo
una sóla vez y pudiera ser utilizado múltiples veces.
Program codigo_q_usa_funcion_hola;
Include “hola.inc”;
Private
Int mivar;
end
Begin
Mivar=mifuncion(3,4);
Write(0,100,100,4,mivar);
Loop
Frame;
end
End
327
CAPITULO 8: TUTORIAL PARA EL JUEGO “SANTA ATTACK”
(extraido del tutorial de Wakroo,en http://divnet.divsite.net)
328
se pueden hacer escenas cinemáticas a base de imágenes fijas
o pequeñas animaciones.
329
tres parámetros, que son respectivamente la cantidad de rojo,
verde y azul que tendrá el color que queramos, desde el
mínimo 0 –no tiene esa componente- hasta el máximo 255, y lo
que devuelve es precisamente el número que usaremos como
tercer parámetro en put_pixel.Por ejemplo, para pintar de
rojo puro el píxel (1,1) en nuestro ordenador concreto,
tendríamos que hacer algo parecido a:
mivar=rgb(255,0,0);
put_pixel(1,1,mivar);
330
A continuación del bucle hay dos sentencias. La primera es
let_me_alone(), que mata todos los procesos menos el que la
ejecuta. La otra es exit("" ), que termina el programa. En
principio la primera sentencia no debería ser necesaria, ya
que se supone que el exit lo hará todo, pero bueno, por si
acaso lo pongo.
La nave:
331
la variable local RESOLUTION=10, que hace que todas las
magnitudes estén multiplica das por 10 (x=1000 sería la
coordenada 100 de pantalla), pero de la manera que lo he
hecho es más simple.
Los propulsores:
332
ANGLE de ese proceso. Pues bien, xadvance hace lo mismo,
desplaza el gráfico de un proceso un número de píxels
determinado –valor que se pone como segundo parámetro-, pero
lo que cambia es el ángulo con que lo hace. Ya no interviene
la variable ANGLE sino que este ángulo viene determinado por
el primer parámetro. Así pues, se puede resumir que esta
función desplaza el proceso actual en la dirección
especificada por el ángulo indicado como primer parámetro, el
número de unidades indicado como segundo parámetro.
333
Todo este lío se ha hecho para conseguir el efecto de que el
fuego "crece y disminuye". Si os fijáis un poco en el juego
el fuego no sale completamente desde el principio, sino que
primero sale más pequeño y luego a tope. A la hora de
apagarse pasa lo mismo pero al revés. El problema que tiene
este sistema es que, en realidad, el fuego va tarde.
Suponiendo que se pulsa la tecla durante un FRAME, en ése
sale un fuego pequeño, en el siguiente más grande y en el
último uno pequeño. Pero el efecto de la impulsión sólo se
dará en el primer FRAME. De todas formas, como es muy poco
probable que alguien consiga una impulsión de un solo FRAME y
era la forma más sencilla de darle un toque bonito decidí que
no importaba ese retraso. Queda el proceso retro(x,y,angle),
pero como funciona prácticamente igual que el proceso
fuego(x,y,angle,graph) no hace falta comentar nada más.
Sigamos adelante con los re galos.
Los regalos:
334
El gráfico sale poco a poco de la tobera hasta ponerse en su
sitio. Para eso en cada vuelta se cogen las coordenadas y
ángulo del padre, se mueve hacia atrás y se gira hasta
ponerse en línea con la tobera. Luego se avanza según cuánto
valga la variable privada aux y se aumenta aux en 2. Con esto
se consigue que a cada FRAME el paquete esté un poco más
fuera, por lo que da la sensación de que se está poniendo en
posición. Finalmente, si aux=24 se rompe el bucle porque ya
estará en su sitio.
335
hasta que se lance, bajará, se pondrá en posición, esperará
hasta que se lance, bajará,... Así para siempre jamás y sin
necesidad de ninguna variable ni código que compruebe si hay
algún r egalo para volver a llamar al proceso, ya que es
único. Con esto ya hemos dejado atrás la parte más
"complicada" del juego. Ahora pasemos a las casas y
chimeneas.
336
Código fuente completo:
/************************** ****************************\
|| Programa: Santa Attack ||
|| Autor: Wakroo ||
|| Grupo: Emerald Works ||
\******************************************************/
Program Santa_Attack;
// Declaración de constantes
Const
337
grav=2; // Gravedad
Global
Int velCasas=2 ; // Velocidad de las casas
Int numCasas=0; // Número de casas
Int puntos=0; // Número de aciertos
Int fuente; // Variable para cargar la fuente de los
textos
// Sonidos
int buiu;
int hit;
int motor;
int fallo;
int victori a;
End
Begin
// Inicialización del modo gráfico a 640x480, 16 bits
set_mode(640,480,16);
buiu=load_wav("buiu.wav");
hit=load_wav("hit.wav");
motor=load_wav("motor.wav");
fallo=load_wav("fallo.wav");
vict oria=load_wav("victoria.wav");
338
// Llamada al proceso intro()
intro();
End
/*****************************************\
| Proceso: intro() |
| Función: Pone las pantallas iniciales |
| Parámetros: |
\*****************************************/
Process intro()
Begin
// Posición y gráfico en pantalla
x=320; y=240; graph=101;
// Cambiar el gráfico
graph=102;
// Encender
fade_on();
// Esperar 50 frames
From z=0 To 49; Frame; End
// Encender la pantalla
339
fade_on();
End
/**************************************************\
| Proceso: juego() |
| Función: Arranca el juego y controla la salida |
| Parámetros: |
\**************************************************/
Process juego();
Begin
// Llamadas a los procesos santa(x,y,graph),
// luna(x,y,graph), crea_casas()
santa(320,240,1);
luna(150,150,21);
crea_casas();
Loop
// Se comprueba si se ha ganado
If (puntos>puntos_victoria)
play_wav(victoria,0);
write(fuente,400,300,4,"¡Has repartido todos los
regalos!");
From z=0 To 100; Frame; End
Break; // Se sale del loop
Else
// Se comprueba si se ha perdido
If (velCasas>velCasas_victoria)
play_wav(fallo,0);
write(fuente,400,300,4,"Has fallado");
From z=0 To 100; Frame; End
Break; // Se sale del loop
End
End
340
// Mata a todos los procesos menos éste
let_me_alone();
// Sale del programa
exit("");
End
/***********************************\
| Proceso: santa(x,y,graph) |
| Función: Protagonista del juego |
| Parámetros: |
| x: posición horizontal |
| y: posición vertical |
| graph: gráfico |
\***********************************/
Process santa(x,y,graph)
Private
Int velx = 0; // Velocidad horizontal
Int vely = -grav; // Velocidad vertical
End
Begin
regalo();
Loop
// Se aumentan x,y con velx,vely / 10
// (para tener más precisión)
// NOTA: también se podría haber usado RESOLUTION
x=velx+velx/10; y=vely+vely/10;
vely=vely+grav; // Se tiene en cuenta la gravedad
// Se rota la nave
If (key(_left)) angle=angle+7500; End
If (key(_right)) angle=angle-7500; End
341
vely=vely+get_disty(angle+90000,prop_fuerte);
// Se llama al proceso fuego(x,y,angle,graph)
fuego(x,y,angle,4);
Else
// Propulsores inferiores a media potencia
If (key(_up))
velx=velx+get_distx(angle+90000,prop_media);
vely=vely+get_disty(angle+90000,prop_media);
fuego(x,y,angle,3);
End
End
// Propulsor trasero
If (key(_alt))
velx=velx+get_distx(angle,prop_media);
vely=vely+get_disty(angle,prop_media);
retro(x,y,angle);
End
Frame;
End
End
/**********************************\
| Proceso: fuego(x,y,angle,graph) |
| Función: Propulsores inferiores |
| Parámetros: |
| x: posición horizontal |
| y: posición vertical |
| angle: ángulo |
| graph: gráfico |
\**********************************/
Process fuego(x,y,angle,graph)
Begin
// Reproduce un sonido
play_wav(motor,0);
342
xadvance(father.angle-90000,18);
xadvance(father.angle,1);
// Se usa el gráfico anterior
graph=graph -1;
Frame;
/******************************\
| Proceso: retro(x,y,angle) |
| Función: Propulsor trasero |
| Parámetros: |
| x: posición horizontal |
| y: posición vertical |
| angle: ángulo |
\************* *****************/
Process retro(x,y,angle)
Begin
play_wav(motor,0);
z = father.z + 2;
xadvance(father.angle+90000,5);
advance(-42);
343
graph=5;
Frame;
Z=z-1;
Graph=graph +1;
angle=father.angle;
x=father.x; y=father.y;
xadvance(father.angle+90000,5);
advance(-42);
Frame;
Graph=graph -1;
Z=z+1;
angle=father.angle;
x=father.x; y=father.y;
xadvance(father.angle+90000,5);
advance(-42);
Frame;
End
/*********************************************\
| Proceso: regalo() |
| Función: Regalos con los que "bombardear" |
| Parámetros: |
\*********************************************/
Process regalo()
Private
Int id2;
Int aux;
End
Begin
Loop
// Gráfico aleatorio
graph=rand(11,15);
aux=0;
344
// Se pone en su sitio (la tobera)
advance( -20);
angle+=210000;
advance(aux);
Frame;
End
advance( -20);
angle=angle+210000;
advance(24);
// Se "dispara" el regalo
If (key(_control)) Break; End
Frame;
End
z=75;
// Se ajusta el ángulo
If (angle>359999) angle=angle-360000; End
345
angle=-90000;
End
End
Frame;
End
End
End
/***************************************\
| Proceso: crea_casas() |
| Función: Proceso que crea las casas |
| Parámetros: |
\***************************************/
Process crea_casas()
Begin
// Crea 3 casas iniciales
casa(960,rand(495,605));
casa(rand(1260,1360),rand(495,605));
casa(rand(1660,1760),rand(495,605));
Loop
// Determina la velocidad de las casas dependiendo
346
// del número de casas que han pasado
velCasas=(numCasas/cantCasas)+1;
Frame;
End
End
/****************************\
| Proceso: casa(x,y) |
| Función: Casa |
| Parámetros: |
| x: posición horizontal |
| y: posición vertical |
\****************************/
Process casa(x,y)
Begin
graph=51;
z=100;
Loop
// Se mueve la casa según la velocidad actual
x=x-velCasas;
Frame;
End
347
/**************** ************************\
| Proceso: chimenea(desplx,desply) |
| Función: Chimenea |
| Parámetros: |
| desplx: posición horizontal con |
| respecto al centro de la casa |
| desply: posición vertical |
| respecto al centro de la casa |
\****************************************/
Loop
// Se asignan las coordenadas del padre
// más el desplazamiento
x=father.x+desplx;
y=father.y+desply;
Frame;
End
End
/****************************\
| Proceso: luna(x,y,graph) |
| Función: Luna del fondo |
| Parámetros: |
| x: posición horizontal |
| y: posición vertical |
| graph: gráfico |
\****************************/
Process luna(x,y,graph)
Begin
z=500;
Loop Frame; End
End
348
CAPITULO 9: TUTORIAL TEÓRICO PARA UN TETRIS
(extraido de Macedonia Magazine,en http://usuarios.lycos.es/macedoniamagazine)
Antes de empezar…:
349
Todas giran hacia la izquierda cada vez que pulsamos la
tecla de giro, es decir, no hay piezas que giren hacia la
izquierda y piezas que lo hagan a la derecha. La pregunta que
nos deberemos de hacer entonces es cómo poder representar de
forma eficiente las piezas y los giros, teniendo siempre
presente las colisiones que se puedan producir (contra otras
piezas o contra los límites del área del juego, esto es, la
tabla en donde vamos alojando las piezas).
350
Así pues, un área como el que sale en la figura
perfectamente puede implementarse con una matriz (tabla
bidimensional) de m filas y n columnas. Ahora la siguiente
pregunta es ¿qué guardo en cada posición de la matriz?. Para
responder a esta pregunta, convendría estudiar también la
forma en la que vamos a implementar las piezas, pues el área
del juego, esto es, la matriz, no hace otra cosa que guardar
las piezas que el jugador ha ido utilizando, y utiliza,
durante la partida. La aproximación más básica nos dice que
hay zonas del área de juego que están ocupadas y otras que no
lo están. Para poder representar esto, bastaría con tener un
valor 0 en las posiciones de la matriz libres y un 1 en las
ocupadas. Sin embargo, como vamos a utilizar piezas de
distintos colores (o, incluso, de distintas propiedades),
convendría generalizar el valor 1 a valores distintos de 0,
de tal forma que valores como el 2, el 3, el 4 o 14, también
indicaran que esa zona está ocupada. Así, si a cada código
distinto de 0 le asignamos un color, podríamos identificar
cada zona de la matriz ocupada por una porción de pieza de un
determinado color. Por ejemplo, veamos la figura siguiente:
351
1. Los que representan casilla vacía, esto es, el conjunto
formado por el código 0.
2. Los que representan presencia de pieza, esto es, los
formados por códigos distintos de 0.
352
Mediante este método construimos las piezas durante el
transcurso de juego, ahorrando espacio en disco y memoria
para gráficos (además los cálculos serán mínimos). Y, sobre
todo, dando una mayor flexibilidad al programa de cara a
modificaciones o inserción de nuevas piezas.
353
continuará hasta que la pieza toque el fondo del área de
juego o bien, hasta que colisione con otra y le sea imposible
seguir bajando.
Colisiones:
354
Colisiones entre los límites del área de juego:
355
Según estos datos, deberemos de contener en todo momento
las posiciones (x,y) de inicio de nuestras piezas para que,
siempre que el usuario vaya a realizar un movimiento,
calculemos por adelantado las nuevas posiciones de nuestras
piezas, entrando en juego las alturas y anchuras propias (que
variarán dependiendo del frame en el que nos encontremos. Por
ejemplo, la barra en horizontal tendrá una anchura de 4
casillas y una altura de 1 casilla y, la barra en vertical
tendrá una altura de 4 casillas y una anchura de 1 casilla.
Todo esto debería de figurar ligado a cada una de las
plantillas de la figura).
356
Colisión con límite izquierdo: Si el usuario pulsa
la tecla de movimiento hacia la izquierda,
deberemos de hacer la comprobación:
357
En este ejemplo, vemos cómo una pieza que intenta
moverse hacia la derecha no puede por el sencillo motivo de
que hay fragmentos de otras piezas que le impiden el
movimiento. Poder controlar esto es muy fácil, pues tan sólo
deberemos de comprobar, internamente, si al mover una
posición nuestra figura (a la izquierda, derecha o hacia
abajo), ocupa alguna zona que ya estaba previamente ocupada.
En este caso, no podremos permitir el movimiento. Si este
tipo de colisión se da, por ejemplo, cuando el temporizador
asignado a la figura que estamos manejando vence y hace que
ésta tenga que bajar una posición, significará que no podrá
bajar y que, por lo tanto, se ha acabado su tiempo de vida en
el juego y tiene que salir otra por la parte superior.
358
bastará saber si en esa área hay alguna posición
del área de juego distinta a 0 (es decir, que
contiene algún fragmento de pieza) ya que, en ese
caso, no podríamos realizar el giro.
359
Aquí podemos ver cómo la barra que acabamos de colocar
hace que dos de sus casillas hagan línea (la casilla 1 y 3,
comenzando a contar de arriba a abajo). ¿Cómo detectar esto?.
Bien, antes de explicar cómo detectar línea, convendría que
pensáramos mejor lo que pasa cuando una pieza hace línea.
Puntos a tener en cuenta:
360
las filas del área de juego. El montón bajará una
altura igual al número de filas que han hecho
línea. En líneas generales, el mecanismo consistirá
en evaluar todas las filas desde la última que ha
hecho línea hacia arriba, marcando, en un
principio, todas las filas como "desocupadas". Una
vez hecho esto, se evaluarán las filas que no hacen
línea (porque son las que tienen las piezas que van
a ser recolocadas) y se irán situando en las filas
marcadas como vacías, hasta que no quede ninguna.
En resumen:
361
CAPITULO 10: TUTORIAL PARA UN JUEGO DE PLATAFORMAS
(extraido de http://www.flamingbird.com)
362
ligeramente diferentes para simular el efecto de caminar.En
concreto, crearemos dos imágenes más, con los códigos 002 y
003.
program plataforma;
global
int idescenario;
end
private
end
begin
set_mode(640,480,16);
set_fps(60,1);
set_title("Mi juego de plataformas");
idescenario=load_fpg("plat.fpg");
start_scroll(0,0,20,0,0,1);
loop
frame;
end
end
Player();
Process player ()
BEGIN
ctype=c_scroll;
scroll.camera=id; /*Esto bloquea la cámara para que siga a
este proceso por pantalla. Es todo lo que
hay que hacer para realizar el scrolling
automáticamente*/
x=600;
y=330; /*Cambia los valores de las coord iniciales según tus
necesidades*/
Graph=1;
LOOP
363
If (key (_right))
x=x+5;
flags=0;
End
If (key (_left))
x=x-5;
flags=1;
End
FRAME;
End
END
Private
Int xx=6;
Int yy=12;
Int iy=1;
end
REPEAT
y=y-(yy-iy);
x=x+xx;
FRAME;
Iy=iy+1;
UNTIL (map_get_pixel(0,27,x,y)==rgb(255,0,0))
iy=1;
End
Esto hace que el jugador salte hacia la derecha de forma
realista cuando se pulse la tecla “s”. Fíjate que el truco
del realismo del salto está en que al principio la coordenada
Y del proceso decrece muy rápido –y la X también-, pero poco
a poco la coordenada Y va decreciendo menos y menos hasta
llegar un punto donde ya no decrece y comienza a crecer poco
a poco a más velocidad: para verlo mejor observa y estudia
qué valores tiene Y en cada iteración del repeat/until en
364
función de lo que vale la variable YY –que no cambia- y lo
que vale IY –que aumenta a cada iteración).El personaje
continuará cayendo hasta que alcance una zona (en el mapa de
durezas) que tenga el color rojo puro. Se supone que el suelo
en el mapa de durezas lo habrás pintado de rojo, tal como
hemos dicho ¿no?, aunque en el juego se vea un precioso prado
verde…
if(key(_right))
x=x+5;
flags=0;
for(cont=3;cont>=1;cont=cont-1)
365
graph=cont;
frame;
end
end
if(key(_left))
x=x-5;
flags=1;
for(cont=3;cont>=1;cont=cont-1)
graph=cont;
frame;
end
end
366
Una vez hecho esto, verás que nuestro personaje, si
salta sobre el precipicio, cae infinitamente y nos quedamos
con el bonito escenario en pantalla sin poder hacer nada de
nada.Lo que nosotros queremos es que una vez que caiga,
nuestro personaje muera. Para eso, tendremos que añadir el
código necesario en el proceso principal, porque aunque
podríamos decir a un proceso que se matara a sí mismo (con la
orden signal(id,s_kill);), es mejor hacerlo desde el proceso
principal para tener este asunto centralizado y así evitar
posibles errores posteriores derivados de despistes (querer
utilizar un proceso cuando se ha suicidado antes, y cosas
así) . Por tanto,añadiremos la siguiente porción de código
dentro del bloque Loop/End del proceso principal, justo antes
del Frame;.
367
ejemplo. Y en el bucle Loop/End del proceso “player()”, justo
antes del Frame; añadimos lo siguiente:
if(map_get_pixel( 0,27,x,y)==rgb(0,0,255))
REPEAT
y=y+5;
frame;
UNTIL (y>=500)
End
if(map_get_pixel( 0,27,x,y)==rgb(0,255,0))
REPEAT
y=y+5;
frame;
UNTIL (map_get_pixel(0,27,x,y)==rgb(255,0,0))
End
368
Todavía falta un detalle más, y es ver qué es lo que
tenemos que hacer para que cuando nuestro personaje está
caminando por una plataforma inferior y salta, si choca de
cabeza con la parte inferior de una plataforma más elevada
rebote y caiga otra vez para abajo. Tal como tenemos ahora el
juego, cuando choca contra una plataforma en mitad del salto,
se mantiene en esa plataforma, y eso no debe ocurrir. Se deja
como ejercicio.
369
CAPITULO 11:TUTORIAL DE DRAG&DROP Y DE GENERACIÓN
DINÁMICA DE IMÁGENES
program demonstracion_dragdrop;
global
370
/*Declaramos una tabla conteniendo todos los caracteres que
necesitaremos para irlos arrastrando*/
String example_text[8] =
'D','r','a','g','&','D','r','o','p';
begin
set_title("Ejemplo de Drag&Drop" );
set_mode(320,240,16);
/*Cambio el color de texto para que sea más bonito que no blanco*/
set_text_color(rgb(150,150,150));
/*El fondo siempre puede ser accesible como el gráfico número 0 de
la librería del sistema (el FPG número 0)*/
map_clear(0,0,rgb(10,10,200));
write_var(0,0,0,0,dragID);
//ratón:
mouse.graph = new_map(30,30,16);
set_center(0,mouse.graph,0,0);
for(x=0;x<30;x++)
for(y=0;y<30;y++)
371
if( (x+y =< 30 and abs(fget_angle(0,0,x,y)-
fget_angle(0,0,30,30)) < 25000 ) or (abs(x-y) < 4 and x+y >
20))
map_put_pixel(0,mouse.graph,x,y,rgb(254,254,254));
end
end
end
mouse.size = 50;
/*El código siguiente crea un gráfico de 1x1 píxel para servir como
puntero efectivo del ratón. En casos donde utilices un puntero de ratón y
planees utilizar comandos de colisión con él, a menudo es mejor utilizar
dos gráficos: uno para el puntero del ratón (típicamente una flecha) y
bajo éste, un gráfico de 1x1 para chequear las colisiones. Haciendo esto
evitarás errores como que puedas arrastrar cosas con la cola de la
flecha, por ejemplo: el puntero efectivo del ratón será una porción mucho
más pequeña y precisa de lo que en realidad de ve por pantalla. En este
ejemplo uso una flecha como gráfi co del puntero del ratón (en la
estructura mouse) pero el programa tiene un proceso representado por
pequeño gráfico que será el puntero efectivo. Dicho gráfico está
coloreado blanco porque píxeles transparentes recuerda que no
colisionan*/
puntero();
/*Aquí es donde los gráficos con las letras se generan y los procesos
“letter()” son creados también. Uso un FOR para generar 9 procesos
“letter()” –x va de 0 a 8-. Como texto uso la tabla declarada al
principio en la sección global y rellenada con letras.*/
for(x=0;x<9;x++)
372
map_put(0,graphic1,graphic2,12,12);
loop
if(key(_esc))exit();end
frame;
end
end
process letter(x,y,graph)
begin
loop
frame;
end
end
process puntero()
begin
graph = new_map(1,1,16);
map_clear(0,graph,rgb(254,254,254));
/*Copio las coordenadas del ratón (ya que allí es donde queremos que esté
el puntero “efectivo”)*/
loop
x = mouse.x;
y = mouse.y;
373
if(exists(dragID))
relativeX = mouse.x - dragID.x;
relativeY = mouse.y - dragID.y;
end
/*Ahora necesitamos arrastrarlo mientras el jugador mantenga
pulsado el botón izquierdo del ratón*/
while(mouse.left)
/*Y mientras eso ocurre, necesitamos no olvidar mantener
sincronizadas las coordenadas del puntero del ratón con las de nuestro
proceso, el puntero “efectivo”, porque si no va a aparecer un punto
blanco fijo mientras hagamos el Drag&Drop*/
x = mouse.x;
y = mouse.y;
if(key(_esc))exit();end
frame;
end //While(mouse.left)
end //If(mouse.left)
frame;
end //Loop
end
374
gráfico de 8 bits, este código de color represe nta como
siempre un número de color de 0 a 255. Para un gráfico de 16
bits, es un número de 0 a 65535 con un código de color que
varía en función del modo gráfico. Puedes utilizar la
Función RGB para obtener la codificación de un color
determinado a partir de sus componentes. En cualquier caso,
ya sabes que el color 0 representa un pixel transparente,
tanto en 8 como en 16 bits.
375
Así pues, la línea mouse.graph = new_map(30,30,16); lo
que hace es asignar a el campo graph de la estructura mouse
( es decir, establecer el icono del puntero del ratón) a una
imagen de 30x30 píxeles, todavía transparente.
376
puntero del ratón –el cual como lo hemos creado con new_map,
pertenece a la librería del sistema- en su coordenada (0,0),
es decir, su esquina superior izquierda.Esto simplemente se
hace para lograr tener un origen “de coordenadas” bueno para
empezar los bucles FOR siguientes de forma que, tal como
están diseñados, funcionen bien.
program Test_MAP_PUT_PIXEL;
begin
set_mode(640,480,16);
graph=new_map(640,480,16);
x=320;
y=240;
write(0,160,190,4,"Pulsa ESC para Salir...");
repeat
map_put_pixel(0,graph,rand(0,639),rand(0,479),rand(1,65536));
377
frame;
until(key(_esc))
end
angle=angle%360000-5625;
378
Ya tenemos el puntero del ratón dibujado, el cual se
moverá allí donde lo mandemos. Pero todavía no hace nada más.
De momento, fíjate que la línea siguiente lo que hace es
disminuir a la mitad el tamaño visible del puntero del ratón
con su propiedad size, para que sea un tamaño más
normal:15x15.
Process letter ()
Private
Int id1;
End
…
If(Id1=collision(type mouse))
…
end
…
end
379
exclusivamente esa carta y no las demás cuando haya
Drag&Drop. Es decir, nosotros necesitaríamos algo parecido a
esto:
Process mouse()
Private
Int id1;
End
…
If(Id1=collision(type letter))
…
end
…
end
380
es el código identificador del gráfico destino –la ficha-, el
tercero es el código identificador del gráfico que se quiere
dibujar dentro del destino –la letra-, y el cuarto y quinto
son las coordenadas X e Y respectivamente del centro del
gráfico a dibujar –la letra - , respecto el gráfico destino,
siendo el punto (0,0) el extremo superior izquierdo de la
imagen destino –la ficha-.
381
Si no hubiera colisión, realmente el código no hace
nada: el proceso continúa siguiendo al puntero y ya está.
Pero si hubiera colisión, lo primero que se hace es fijar
unas cantidades (“relativeX” y “relativeY”) que representarán
la distancia entre el puntero del ratón y el centro de la
ficha colisionada, en el momento de apretar el botón
izquierdo. Este valor constante simplemente sirve para que
cuando arrastremos alguna ficha, el centro de su gráfico no
se acople con la punta de la flecha –es el comportamiento
normal, pero queda feo- sino que la posición relativa de su
centro respecto la punta de la flecha se mantenga igual al
principio del Drag&Drop correspondiente.
382
CAPITULO 12: ARTICULOS,CODIGOS Y TRUCOS VARIADOS
Este capítulo pretende ser una miscelanea, un cajón de
sastre donde pueden tener cabida los más variados temas sobre
la programación de Fénix: trucos, ideas,
artículos,sugerencias,etc. No pretende tener ninguna
coherencia interna como los capítulos anteriores: simplemente
que aporte una referencia rápida de consulta para aquel
aspecto o duda concreta que nos pudiera surgir a la hora de
programar. Tampoco pretende ser un resumen exhaustivo: la
mayoría de información recogida y recopilada aquí se ha
extraido del foro de Fenix (http://forum.divsite.net), por lo
que en cualquier caso es ese lugar el más idoneo para
preguntar las dudas y problemas, y donde se aportarán las
soluciones más eficaces y actualizadas.
383
caracteres, sólo los más usuales en el idioma inglés. Por
ejemplo, el código ASCII del carácter ‘A’ (letra a mayúscula)
sería el número 65, del código de ‘B’ sería el 66, el del
dígito ‘0’ el 48, el del dígito ‘1’ el 49, el del carácter
‘a’ (letra a minúscula) el 97, el de la ‘b’ el 98, el del
espacio en blanco el 32,etc. Si quieres saber cuál es el
código ASCII de cualquier carácter es muy fácil: o haces un
programa que te saque toda la lista, o bien en cualquier
editor de textos o barra de direcciones del explorador
tecleas el código que quieras con el teclado numérico del
teclado mientras mantienes pulsada la tecla ALT. Y ya verás
que se escribe el carácter correspondiente. Por ejemplo,
algunas direcciones de páginas web tienen el carácter ~. Este
carácter no aparece en ninguna tecla del teclado; por lo que
el truco está en imprimirlo utilizando su código ASCII, que
es el 126.
384
tecla ENTER, pero no es lo mismo una tecla que un carácter
(en la tecla “A” se pueden imprimir dos caracteres
diferentes: la a mayúscula y la a minúscula.Por si lo quieres
saber, el carácter ENTER viene representado por el número 13
(retorno de carro).
program hola;
private
string cadena;
end
begin
repeat
if(ascii!=0)
cadena=cadena+chr(ascii);
write_var(0,100,100,4,cadena);
end
frame;
until(key(_enter))
loop
frame;
end
end
385
No dejar de mencionar la existencia de la línea frame;,
imprescindible entre otras cosas para que la variable ASCII
pueda actualizarse. Y por último, comento el por qué del if.
Si no lo pusiéramos, el programa funcionaria igualmente, pero
lo que haría es que en cada frame que no se pulsara tecla
alguna, concatenaría a la cadena el carácter que tiene el
código ASCII 0 –es un carácter especial que no se imprime-,
porque la variable ASCII en esos frames siempre vale 0. Por
tanto, estaríamos alargando inecesariamente “cadena” con
caracteres inútiles hasta el infinito, y a la larga el
programa petaría porque no podría manejar una cadena tan
larga. Por tanto, el if sirve para detectar que si no hay
tecla pulsada en ese frame, que no hace falta añadir ningún
carácter a la cadena.
program hola;
private
int letra_ant;
string cadena;
end
begin
loop
if (ascii!=letra_ant)
switch (ascii)
//para que no haga nada si no se pulsa una letra
case 0: end
386
//para que la tecla de retroceso haga su tarea
case 8 :
cadena=substr(cadena,0,len(cadena)-
1);
end
//aquí podríamos escribir el códi go adecuado al carácter ESC (el de la tecla ESC)
case 27: end
387
Es más fácil verlo con un ejemplo. Si tenemos la línea
substring(“Hola qué tal”,2,3), lo que devuelve esta línea
será la cadena “la “. Porque lo que se ha hecho es, a partir
de la cadena original, ir al carácter que ocupa la posición
2, y a partir de él, coger los tres caracteres siguientes.
Como el primer carácter es el número 0, el carácter 2 es la
ele. Y como hemos dicho que se quieren coger 3 caracteres,
se coge la ele, la a y el espacio en blanco. Y ésta es la
subcadena obtenida.
388
Y volviendo al código, ves que si no se pulsa ninguna de
estos caracteres especiales, es entonces cuando ejecutamos el
default, y por tanto, imprimimos por pantalla el carácter
adecuado.
389
Utilizar temporizadores:
process controlTiempo()
begin
tiempo = 100;
timer[0] = 0;
repeat
tiempo = 100 - (timer[0] / 100);
390
delete_text(0);
write_var(0,100,100,4,tiempo);
if (tiempo == 0)
estadoJuego = FINPARTIDA;
end
frame;
until (estadoJuego == FINPARTIDA)
end
391
Program ejemplo_timer_para_record;
Global
int mitiempo=0;
end
Begin
set_mode(640,480,16);
record();
End
Process record()
Begin
write(0,320,240,1,"PULSA ENTER para PARAR EL TIMER");
Loop
Repeat
mitiempo=timer[0];
write_var(0,320,450,1,timer[0]);
Frame;
Until(key(_enter))
Break;
End
delete_text(0);
write(0,320,100,1,"TU TIEMPO");
write(0,320,250,1,"pulsa Esc para Salir");
write_var(0,320,125,1,mitiempo);
Loop
If(key(_esc)) exit();End
Frame;
End
End
Opciones de depuración:
392
aquello que se supone que deberían de valer, y poderlo
corregir.
FXC –g mijuego.prg
393
Trucos comunes usando el ratón:
program prueba;
begin
set_mode(640,480,16);
set_fps(30,0);
observador();
destino();
loop
frame;
end
end
process observador()
private
int angulo;
end
begin
x=320;y=240;
graph = load_png("observador.png");
loop
angle=get_angle(get_id(type destino));
frame;
end
end
process destino()
begin
graph = load_png("raton.png");
loop
x=mouse.x;
y=mouse.y;
frame;
end
end
end
394
A lo mejor a ti se te hubiera ocurrido poner
angle=get_angle(mouse);. Sin embargo, no habría funcionado
porque mouse no es un proceso,es una estructura.
Loop
if(mouse.left==true)
posx=mouse.x;
posy=mouse.y;
mouse.left=false;
end
if(x>posx); x=x-1; end
if(x<posx); x=x+1; end
if(y>posy); y=y-1; end
395
if(y<posy); y=y+1; end
frame;
end
Program prueba;
Private
Int fichero1;
end
Begin
set_mode(640 ,480,16);
fichero1=load_fpg("bola.fpg");
write(0,10,20,0,"DIBUJA CON EL MOUSE");
definemouse();
While (NOT key(_esc))
Frame;
End
fade_off();
exit("");
End
//---------------------------------------------
Process dibuja(x,y)
Begin
flags=4;
graph=100;
Loop
Frame;
End
End
396
//--------------------------------------------
Process definemouse()
Begin
mouse.x=270;
mouse.y=240;
Loop
graph=100;
If (mouse.x>640) mouse.x=640; End
If (mouse.x<0) mouse.x=0; End
If (mouse.y>480) mouse.y=480; End
If (mouse.y<0) mouse.y=0; End
If (mouse.left) dibuja(mouse.x,mouse.y); End
x=mouse.x;
y=mouse.y;
Frame;
End
End
Begin
mivar1=10;
while(mivar1<320)
delete_text(0);
mivar1=mivar1+2;
write(0,mivar1,100,1,"¡Hola mundo!");
frame;
end
end
397
Recuerda que lo que hacía este programa era hacer un
scroll horizontal de la frase “¡Hola mundo!”. Podríamos ahora
modificar este programa para que haga exactamente lo mismo,
pero utilizando la función move_text. Es más, esta nueva
versión incluso sería “mejor” que la primera porque se pueden
evitar los posibles parpadeos que se producen cuando se borra
y escriben los te xtos constantemente a una elevada velocidad,
tal como se hace en el programa original.Bien, pues está
claro que la versión con move_text será así:
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
write(0,10,100,1,"¡Hola mundo!");
while(mivar1<320)
mivar1=mivar1+2;
move_text(1,mivar1,100);
frame;
end
end
398
Bueno, ahora cambiaremos un poco el programa y
añadiremos otro texto que diga “¡Y adiós!”, pero que
permanecerá fijo en pantalla.Y lo que haremos será cambiarle
el color.Así:
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=10;
write(0,10,100,1,"¡Hola mundo!");
set_text_color(25);
write(0,250,50,1,”¡Y adiós!”);
while(mivar1<320)
mivar1=mivar1+1;
move_text(1,mivar1,100);
frame (25);
end
end
399
Program MiPrimerPrograma;
Private
int mivar1;
End
Begin
mivar1=0;
write(0,10,100,1,"¡Hola mundo!");
set_text_color(25);
write(0,320,50,1,”¡Y adiós!”);
while(mivar1<320)
mivar1=mivar1+2;
move_text(1,mivar1,100);
move_text(2,320-mivar1,50);
frame (25);
end
end
Program lala;
private
int var=10;
int idtexto;
end
Begin
set_mode (800,600,16);
idtexto=write(0,100,30,4,"texto oscilante");
Loop
var=var+22500;
move_text(idtexto,100+get_disty(var,20),30);
Frame;
400
If(key(_esc)) Break; End
End
End
401
El gráfico, pues, contendrá el texto especificado como
parámetro, en el tipo de letra indicado, y tendrá el tamaño
justo para contener todas las letras sin desperdiciar espacio.
El nuevo gráfico tendrá la misma profundidad de color que el
tipo de letra utilizado, y pixels transparentes al igual que
los propios caracteres de la fuente.
402
FOPEN(“fichero”,modo)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
403
En cambio, con el modo O_WRITE, todo lo que se escriba
en el fichero sobreescribirá completamente el contenido
anterior desde el principio porque éste se borra totalmente.
FSEEK(fichero,posicion,desde)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
404
posición actual (un número positivo o negativo) –cuyo valor
se puede obtener con FTELL- o a partir del final del mismo
(un número negativo o 0). En todo caso, la posición se
ajustará en función del tamaño actual del fichero, y se
devolverá la nueva posición real, siempre respecto al origen
del fichero.
FWRITE( fichero,variable)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
FREAD(fichero,variable)
PARÁMETROS
VALOR DE RETORNO
405
DESCRIPCIÓN
FEOF(fichero)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
406
Esta función devuelve 1 si el puntero de
lectura/escritura traspasa el final de un fichero abierto con
la función FOPEN, o 0 en caso contrario.
FCLOSE(fichero)
PARÁMETROS
DESCRIPCIÓN
program fafafa;
private
int i;
int idfich;
struct mistruct[2]
int a;
string b;
end =1,"hola",2,"que tal",3,"muy bien";
end
begin
idfich=fopen("pepito.txt",o_write);
fwrite(idfich,mistruct);
fclose(idfich);
end
407
En este ejemplo hemos utilizado una tabla de estructuras
para reducir el código del ejemplo y remarcar así lo
importante sin perdernos en otros detalles, pero lo normal
trabajando con archivos es utilizar estructuras simples.
program fafafa;
private
int i;
int idfich;
struct mistruct[2]
int a;
string b;
end = 1,"ah, si?",2,"pues yo",3,"también";
end
begin
idfich=fopen("pepito.txt",o_readwrite);
fseek(idfich,0,2);
fwrite(idfich,mistruct);
fclose(idfich);
end
408
moverá 0 bytes (2º parámetro) –o sea, que se quedará en el
final-.
program fafafa;
private
int i=0;
int idfich;
struct mistruct
int a;
string b;
end
end
begin
idfich=fopen("pepito.txt",o_read);
loop
fread(idfich,mistruct);
if (feof(idfich)!=false) break; end
write(0,100,(i*10)+50,4,mistruct.a);
write(0,150,(i*10)+50,4,mistruct.b);
i=i+1;
end
fclose(idfich);
loop
frame;
end
end
409
sabemos que ya no quedan más estructuras por leer? Cuando
llegamos al final del fichero, y esto ocurre cuando la
función feof devuelve 1 (true).
program fafafa;
private
int i=0;
int idfich;
struct mistruct
int a;
string b;
end
end
begin
idfich=fopen("pepito.txt",o_read);
while(feof(idfich)==false)
fread(idfich,mistruct);
write(0,100,(i*10)+50,4,mistruct.a);
write(0,150,(i*10)+50,4,mistruct.b);
i=i+1;
end
fclose(idfich);
loop
frame;
end
end
410
manera, si has te has pasado del final, lo sabrás de
inmediato y el programa podrá reaccionar a tiempo.
FPUTS(fichero,”texto”)
PARÁMETROS
DESCRIPCIÓN
FGETS(fichero)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
411
cadena, sin incluir el carácter o caracteres de final de
línea.
program fafafa;
private
int idfich;
end
begin
idfich=fopen("pepito.txt",o_write);
fputs(idfich,”hola, ¿qué tal?. Muy bien.”);
fputs(idfich,”¿Ah,sí? Pues yo también”);
fclose(idfich);
end
412
program fafafa;
private
int idfich;
string cadenatotal="";
string cadenaleida;
end
begin
idfich=fopen("pepito.txt",o_read);
loop
if(feof(idfich)!=false) break; end
cadenaleida=fgets(idfich);
cadenatotal=cadenatotal+cadenaleida;
end
fclose(idfich);
write(0,160,50,4,cadenatotal);
loop
frame;
end
end
Fíjate cómo vamos concatenando en cada iteración la
cadena recientemente leída a la cadena ya creada,
actualizando de esta manera la propia cadena “total”. Éste es
el método típico para ir añadiendo porciones de texto a una
cadena que por lo tanto va aumentando de longitud.
FTELL(fichero)
PARÁMETROS
VALOR DE RETORNO
413
DESCRIPCIÓN
FILE_EXISTS(“fichero”)
PARÁMETROS
DESCRIPCIÓN
CD
VALOR DE RETORNO
DESCRIPCIÓN
414
archivos como parámetro de dichas funciones y sólo se
especifique el nombre).
CHDIR(“directorio”)
PARÁMETROS
DESCRIPCIÓN
MKDIR(“directorio”)
PARÁMETROS
DESCRIPCIÓN
415
En los sistemas que así lo permitan, no se podrán crear
directorios en la ruta indicada si no se tienen los permisos
adecuados.
RMDIR(“directorio”)
PARÁMETROS
DESCRIPCIÓN
GLOB(“patrón”)
PARÁMETROS
VALOR DE RETORNO
416
DESCRIPCIÓN
program Test_GLOB;
Private
String archivo; //cadena donde se guarda el nombre del archivo
Int cont=0;
Begin
chdir("../"); //seleccionamos una carpeta, que es la superior a la actual
archivo=glob("*.???"); /*buscamos un archivo cuyo nombre respete el
patrón indicado*/
While (archivo!="") //comprobamos que ha encontrado algun archivo
417
Write(0,10,cont*10,0, archivo); /*escribimos el nombre del
archivo…*/
Write(0,250,cont*10,0,FILEINFO.CREATED);/*y su fecha y
hora de creación*/
cont=cont+1; //avanzamos una linea
archivo=glob("*.???"); //buscamos otro archivo
End
//y esperamos que se pulse escape para salir
Loop
If (Key(_ESC)) break; end
Frame;
End
End
LCASE(“cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
418
Devuelve una cadena idéntica a la original, pero
convirtiendo todas las letras mayúsculas a minúsculas
(incluyendo letras con acentos o tildes).
UCASE(“cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
Devuelve una cadena idéntica a la original, pero
convirtiendo todas las letras minúsculas a mayúsculas
(incluyendo letras con acentos o tildes).
STRCASECMP(“cadena”,”cadena2”)
VALOR DE RETORNO
DESCRIPCIÓN
419
Un valor positivo indica que el primer carácter
diferente entre las dos cadenas por orden alfabético va
después en la segunda cadena que en la primera.
STRREV(“cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
LPAD(“cadena”,tamaño)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
420
También existe la función RPAD, que expande una cadena
añadiendo espacios a la derecha, hasta el tamaño dado.
Funciona igual que LPAD.
TRIM(“cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
ATOI(“cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
421
serán ignorados. Si la cadena comienza por caracteres que no
corresponden a dígitos, la función no detectará el número y
devolverá 0.
ITOA(valor)
PARÁMETROS
INT VALOR : Número a convertir
VALOR DE RETORNO
DESCRIPCIÓN
FORMAT(valor,decimales)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
422
respectivamente, pero usando números en coma flotante
(decimales)
program la;
private
int entera=4;
float decimal=9.82;
string cadena="123Hola";
string cadena2="12.3Hola";
end
begin
set_mode(640,480);
loop
delete_text(0);
write(0,250,50,4,"Conversión de un entero en
cadena: "+ itoa(entera));
write(0,250,80,4,"Conversión de una cadena a
entera: "+atoi(cadena));
write(0,250,110,4,"Conversión de un número decimal
en cadena: "+ftoa(decimal));
write(0,250,140,4,"Conversión de un número decimal
a cadena con determinados decimales: "+format(decimal,4));
write(0,250,170,4,"Conversión de una cadena a
número decimal: "+atof(cadena2));
frame;
end
end
FIND(“cadena”,”buscar”,posición)
PARÁMETROS
VALOR DE RETORNO
423
INT : Posición del primer carácter de la primera
aparición de la cadena buscada, o -1 si no se encontró
DESCRIPCIÓN
program Test_FIND;
global
string Txt="¡Hola Mundo, hola!";
begin
write(0,10,10,3,"Test FIND para Fenix...");
write(0,10,30,3,"Texto = ¡Hola Mundo, hola!");
write(0,10,40,3,"FIND(H) = "+itoa(find(Txt,"H")));
write(0,10,50,3,"FIND(Hola) = "+itoa(find(Txt,"Hola")));
write(0,10,60,3,"FIND(Hola a partir del 5 caracter) =
"+itoa(find(Txt,"Hola",4)));
write(0,10,70,3,"FIND(M) = "+itoa(find(Txt,"M")));
write(0,10,80,3,"FIND(Mundo) = "+itoa(find(Txt,"Mundo",-
2)));
write(0,10,90,3,"FIND(!) = "+itoa(find(Txt,"!")));
write(0,10,100,3,"FIND(O) = "+itoa(find(Txt,"O")));
write(0,160,190,4,"Pulsa ESC para Salir...");
repeat
frame;
424
until(key(_esc))
end
REGEX(“expresión”,”cadena”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
425
lista", y en su interior puede haber una serie de
caracteres corrientes, o bien rangos de caracteres. Por
ejemplo "[abc]" permite encontrar "a", "b" ó "c",
mientras que "[a-z]" permite encontrar cualquier letra
entre la "a" y la "z", inclusives. Los rangos pueden
ser inacabados: "[- ]" permite encontrar cualquier
carácter hasta el espacio, mientras "[ -]" permite
encontrar cualquier carácter del espacio en adelante.
Además, se reconocen "clases de caracteres" POSIX, que
son secuencias predefinidas de caracteres que se
escriben con la sintaxis "[:clase:]" y pueden ser
cualquiera de las siguientes:
426
"+": encuentra 1 o más repeticiones de lo anterior, del
último carácter o metacaracter anterior a este símbolo.
"[abc]+" encuentra "aabbcc" y "bccba", pero no "defg".
REGEX_REPLACE(“expresión”,”cambia”,”cadena ”)
PARÁMETROS
VALOR DE RETORNO
DESCRIPCIÓN
427
Dada una expresión regular, comprueba si la cadena dada
la cumple en algún punto, y sustituye lo encontrado por el
nuevo texto indicado como tercer parámetro. Devuelve la
cadena resultante (no se modifica la original).
program regex_replace_test;
global
string result;
end
begin
result=REGEX_REPLACE("caca","bombon","el caca comia caca
con caca y nueces");
write(0,10,10,0,result);
repeat
frame;
until(key(_enter))
end
Y otro:
program regex_replace_test;
global
string result;
end
begin
result=REGEX_REPLACE("ca+","p","el caca comia caca con
caca y nueces");
write(0,10,10,0,result);
repeat
frame;
until(key(_enter));
end
428
Y otro un poco más complejo:
program regex_replace_test;
global
string result;
end
begin
result=REGEX_REPLACE("(ca|co)","p","el caca comia caca
con caca y nueces");
write(0,10,10,0,result);
repeat
frame;
until(key(_enter))
end
TIME()
DESCRIPCIÓN
429
FTIME(“formato”,time)
PARÁMETROS
DESCRIPCIÓN
%e Día, 1 o 2 dígitos
%d Día, 2 dígitos
%m Mes, 2 dígitos
%y Año, 2 dígitos
%Y Año, 4 dígitos
%k Hora, 1 o 2 dígitos
%H Hora, 2 dígitos
%M Minutos, 2 dígitos
%S Segundos, 2 dígitos
%j Día del año, 3 dígitos
%U Número de semana del año, 2 dígitos
program Test_Ftime;
begin
write(0,10,10,3,"Test ftime para Fenix...");
write(0,160,390,4,"Pulsa ESC para Salir...");
write(0,10,60,3,"%d - Día, dos dígitos:
"+ftime("%d",time()));
430
write(0,10,70,3,"%e - Día, uno o dos dígitos:
"+ftime("%e",time()));
write(0,10,80,3,"%m - Mes, dos dígitos:
"+ftime("%m",time()));
write(0,10,90,3,"%y - Año, dos dígitos:
"+ftime("%y",time()));
write(0,10,100,3,"%Y - Año, cuatro dígitos:
"+ftime("%Y",time()));
write(0,10,110,3,"%H - Hora, dos dígitos:
"+ftime("%H",time()));
write(0,10,120,3,"%k - Hora, unos o dos dígitos:
"+ftime("%k",time()));
write(0,10,130,3,"%M - Minuto, dos dígitos:
"+ftime("%M",time()));
write(0,10,140,3,"%S - Segundo, dos dígitos:
"+ftime("%S",time()));
write(0,10,150,3,"%j - Día del año, tres dígitos:
"+ftime("%j",time()));
write(0,10,160,3,"%U - Numero de semana del año:
"+ftime("%U",time()));
repeat
frame;
until(key(_esc))
end
Funciones matemáticas:
ABS(número)
PARÁMETROS
431
DESCRIPCIÓN
COS(número)
PARÁMETROS
DESCRIPCIÓN
ACOS(número)
PARÁMETROS
DESCRIPCIÓN
SQRT(número)
432
PARÁMETROS
DESCRIPCIÓN
POW(número,potencia)
PARÁMETROS
DESCRIPCIÓN
RAND_SEED(número)
433
Para evitar esto, y hacer que cada vez que un programa
que incluya la función RAND genere ristras de números
completamente diferentes, Fénix hace que automáticamente el
valor tomado como semilla de la función RAND sea la hora (en
segundos) actual.Evidentemente, este número cada segundo
cambiará, por lo que todos los programas que ejecutemos en
segundos diferentes tendrán ristras de números diferentes.
434
final d el fichero y escribe un carácter que no sea un número.
Éste será la marca que indicará el comienzo de una serie
nueva. Vuelve a ejecutar el programa, y vuelve a abrir el
fichero. Verás que justo después de la marca que habías
escrito vuelve a aparecer una serie de números, que no
tienen nada que ver con los de la serie anterior. Esto es
porque RAND automáticamente coge la semilla de la hora del
sistema, y la hora del sistema ha cambiado entre las dos
ejecuciones del programa.
Rand_seed(3);
435
dibujar los círculos y rectángulos y luego utilizar el FPG
correspondiente, pero en el caso de estas figuras tan
sencillas de dibujar, Fénix ofrece la posibilidad de crearlas
mediante código mientras el programa se ejecuta, mejorando
así la velocidad de ejecución, y también la comodidad del
programador, que no tiene que recurrir a dibujos externos
creados previamente.
DRAWING_MAP(librería, gráfico)
PARÁMETROS
DESCRIPCIÓN
DRAW_RECT(X1,Y1,X2,Y2)
Dibuja un rectángulo
PARÁMETROS
436
DESCRIPCIÓN
DRAWING_COLOR(color)
PARÁMETROS
DESCRIPCIÓN
437
DRAWING_ALPHA(alpha)
PARÁMETROS
DESCRIPCIÓN
program Test_DRAW_RECT;
begin
set_mode(640,480,16);
drawing_map(0,0);
repeat
drawing_color(rand(0, 65535));
drawing_alpha(rand(0,255));
draw_rect(rand(0,639),rand(0,479),rand(0,639),rand(0,479));
frame;
until(key(_esc))
end
438
solamente los trozos que se pinten sobre un gráfico
determinado. Haz el siguiente ejercicio: crea un FPG llamado
“mifpg.fpg” con dos gráficos. El primero, con código 001 y de
dimensiones menores que la ventana, representará una zona de
ésta donde queremos que se vean los rectángulos que
pintaremos aleatoriamente; el segundo, con código 002 y
dimensiones iguales a la ventana, representará el fondo de la
pantalla, donde no se pintará ningún rectángulo. Y escribe
esto:
program Test_DRAW_RECT;
global
int idfpg;
end
begin
set_mode(640,480,16);
idfpg=load_fpg("mifpg.fpg");
put_screen(idfpg,2);
drawing_map(idfpg,1);
pepito();
repeat
drawing_color(rand(0,65535));
drawing_alpha(rand(0,255));
draw_rect(rand(0,639),rand(0,479),rand(0,639),rand(0,479));
frame;
until(key(_esc))
end
process pepito()
begin
file=idfpg;
graph=1;
loop
frame;
end
end
439
A parte de rectángulos, también podemos dibujar más
cosas:
DRAW_CIRCLE(X,Y,RADIO)
PARÁMETROS
DESCRIPCIÓN
DRAW_LINE(X1,Y1,X2,Y2)
PARÁMETROS
DESCRIPCIÓN
440
DRAWING_S TIPPLE(valor)
PARÁMETROS
DESCRIPCIÓN
DRAW_CURVE(X1,Y1,X2,Y2,X3,Y3,X4,Y4,nivel)
PARÁMETROS
DESCRIPCIÓN
441
Usa el color escogido por elegido con DRAWING_COLOR, en
el gráfico escogido por la última llamada a DRAWING_MAP.
Además, se utiliza el último valor especificado con
DRAWING_ALPHA como nivel de transparencia para el dibujo, de
manera que es posible dibujar una curva parcialmente
transparente. Es válido especificar coordenadas fuera de los
límites del gráfico, pero el dibujo resultante se recortará
sobre éstos.
DRAW_BOX(X1,Y1,X2,Y2)
PARÁMETROS
DESCRIPCIÓN
DRAW_FCIRCLE(X,Y,RADIO)
PARÁMETROS
442
DESCRIPCIÓN
program Test_DRAW ;
begin
set_mode(640,480,16);
set_fps(1,1);
drawing_map(0,0);
repeat
drawing_color(rand(0,65535));
drawing_alpha(rand(0,255));
draw_circle(rand(0,639),rand(0,479),rand(0,320));
frame;
draw_fcircle(rand(0,639),rand(0,479),rand(0,320));
frame;
//drawing_stipple(rand(0,65535)); frame;
draw_line(rand(0,639),rand(0,479),rand(0,639),rand(0,479));
frame;
draw_curve(rand(0,639),rand(0,479),rand(0,639),rand(0,479),ra
nd(0,639),rand(0,479),rand(0,639),rand(0,479),9);
frame;
draw_box(rand(0,639),rand(0,479),rand(0,639),rand(0,479));
frame;
until(key(_esc))
end
443
Program ejemplo_get_pixel;
Global
int color;
int fichero1;
end
Begin
set_mode (800,600,16);
fichero1=load_fpg("paleta.fpg");
put_screen(0, 1);
mouse.graph=2;
write (0, 260, 180, 0, "Color");
write_var(0, 260, 200, 0, color);
Loop
delete_draw(0);
drawing_color(color);
draw_box(320,180,380,220);
// Cogemos el color del punto del fondo de la pantalla
color = map_get_pixel(0,0,mouse.x, mouse.y);
Frame;
If(key(_esc)) Break; End
End
End
444
¿Y qué ganamos con esta manera de trabajar? Pues poder
utilizar dos nuevas funciones: DELETE_DRAW y MOVE_DRAW, las
cuales no podríamos usar si trabajáramos con DRAWING_MAP, y
que sirven, respectivamente, para borrar y mover en la
pantalla una primitiva concreta.
DRAWING_Z(Z)
PARÁMETROS
DESCRIPCIÓN
445
Para desactivar este modo y volver a hacer dibujos
directamente sobre el fondo de pantalla u otro gráfico, es
preciso llamar a la función DRAWING_MAP cuando se necesite.
DELETE_DRAW(primitiva)
PARÁMETROS
DESCRIPCIÓN
MOVE_DRAW(primitiva,X,Y)
PARÁMETROS
DESCRIPCIÓN
446
Las coordenadas especificadas hacen referencia al
primero de los puntos utilizados para dibujar una línea o
curva, o bien al centro del círculo.
program Test_DRAW;
global
int idcirc;
end
begin
set_mode(640,480,16);
set_fps(1,1);
drawing_map(0,0);
repeat
drawing_color(rand(0,65535));
drawing_alpha(rand(0,255));
idcirc=draw_circle(rand(0,639),rand(0,479),rand(0,320));
frame;
move_draw(idcirc,400,400);
frame;
until(key(_esc))
end
program Test_DRAW;
global
int idcirc;
end
begin
set_mode(640,480,16);
447
set_fps(1,1);
drawing_z(1);
repeat
drawing_color(rand(0,65535));
drawing_alpha(rand(0,255));
idcirc=draw_circle(rand(0,639),rand(0,479),rand(0,320));
frame;
move_draw(idcirc,400,400);
frame;
until(key(_esc))
end
program Test_DRAW;
private
int idcircle;
end
begin
set_mode(640,480,16);
drawing_z(1);
repeat
delete_draw(idcircle);
drawing_color(rand(0,65535));
idcircle=draw_circle(320,240,200);
frame;
until(key(_esc))
end
448
siempre en memoria, con la función fpg_add. El valor que
devuelve es el código de la nueva librería FPG. Finalmente,
todos los gráficos añadidos a una misma librería deben tener
la misma profundidad de color (1, 8 o 16 bits): si no se
produce un error.
449
program aver;
private
int idfpg1;
int idfpg2;
end
begin
idfpg1=load_fpg("antiguo.fpg");
idfpg2=fpg_new();
fpg_add(idfpg2,1,idfpg1,2);
fpg_add(idfpg2,2,idfpg1,1);
save_fpg(idfpg2,"nuevo.fpg");
end
PROGRAM TP_SAVE_PNG;
global
int fichero1;
int a=0;
end
begin
fichero1 = load_fpg("test.fpg");
set_mode(320,240,16);
set_fps(150,0);
450
put_screen(fichero1,1);
loop
save_png(0,0,"tp_save_png.png");
a=a+100;
map_xput(0,0,load_png("tp_save_png.png"),160,120,a,99,7);
frame;
end
end
process proceso(x,y)
begin
graph = new_map(640,480,16);
loop
angle=angle+100;
save_png(0,0,"tp_save_png.png");
frame;
end
end
UNLOAD_MAP(librería,gráfico)
PARÁMETROS
DESCRIPCIÓN
451
Es posible utilizar esta función para quitar gráficos de
un fichero FPG (lo contrario de fpg_add). Los cambios
realizados por esta función sólo afectan al FPG en memoria:
para actualizar el fichero FPG con ellos recuerda que es
preciso usar la función save_fpg .
FPG_EXISTS(librería)
PARÁMETROS
DESCRIPCIÓN
MAP_EXISTS(librería, gráfico):
PARÁMETROS
DESCRIPCIÓN
GRAPHIC_INFO(librería,gráfico,tipo)
PARÁMETROS
452
INT GRÁFICO : Número de gráfico dentro de la
librería
INT TIPO : Tipo de característica a consultar.
Puede ser, entre otras que no citaré:
G_WIDTH :Ancho en pixels del gráfico
G_HEIGHT :Alto en pixels del gráfico
G_CENTER_X :Coordenada horizontal del centro
G_CENTER_Y :Coordenada vertical del centro
G_DEPTH :Profundidad de color (en bits por
pixel)
DESCRIPCIÓN
GRAPHIC_SET(librería, gráfico,tipo,valor)
PARÁMETROS
DESCRIPCIÓN
GET_SCREEN()
453
VALOR DE RETORNO
DESCRIPCIÓN
PUT(librería, grafico,x,y)
PARÁMETROS
DESCRIPCIÓN
454
XPUT(librería, gráfico,x,y,ángulo,tamaño,flags,region):
PARÁMETROS
DESCRIPCIÓN
WINDOW_STATUS
455
Conceptos sobre física newtoniana:
x=x0 +v0x*t;
456
Además de ese movimiento, tenemos la componente Y, que
es un movimiento vertical (la bomba sube y baja con cierta
aceleración o desaceleración), cuya fórmula es:
y= y0 + voy*t + 0.5*g*t*t
457
x=x0 +v0x*t; luego: 20 = 300 +v0x*60;
458
Y que el proceso enemigo es el que crea la bala.
BEGIN
x = father.x;
y = father.y;
t=60;
g=9.8;
/*La gravedad vale 9.8 segundos, pero nosotros las fórmulas las
estamos calculando tomando como unidad de tiempo el fotograma. Así que
hay que transformar la gravedad en unidades de fotograma.*/
gravedad_por_frame = g/t;//Ha de ser un valor constante
459
proyectil volará HASTA que impacte con algo). Tienes razón.
Para ello, deberíamos quitar el bloque FROM/END y poner en su
lugar:
REPEAT
x = x + v0x; //movimiento X
y = y + vy; //movimiento Y
vy = vy + gravedad_por_frame; //aceleración vertical
frame;
UNTIL (...) /*fin del bucle. Aquí se escribirá la condición de
ruptura: haber impactado con el escenario o con el
enemigo.*/
460
código que acabamos de escribir, la velocidad puede crecer
tanto que la trayectoria del misil no se vea. Para evitar
esto bastaría un if:
Const
sc_width=640; //La anchura y altura de la pantalla
sc_height=480;
End
461
if((x+v_x >= sc_width - graphic_info (0, graph,
G_WIDTH)/2) || (x +v_x <= graphic_info (0, graph,
G_WIDTH)))
v_x = -v_x;
end
if(y+v_y >= sc_height - graphic_info (0, graph,
G_HEIGHT)/2)
v_y = -v_y;
end
if(key(_esc))exit(); end;
v_y =v_y+g;
x =x+v_x; //Actualizamos nuestra posición
y =y+v_y;
frame;
End
End
462
alpha). Ahora imagina que queremos poner un enemigo en el
punto del semicírculo donde llega el radio que tiene ese
ángulo alpha.Su X sería el coseno, y su Y el seno.Pero como
el radio no siempre será 1, entonces haremos lo siguiente:
x = radio * coseno(alpha)
y = radio * coseno(alpha)
463
Para el ejemplo vamos a crear dos tablas, “camino_x” y
“camino_y”, donde almacenaremos los resultados de calcular
y=r*seno y x=r*coseno. Ya que estos resultados solamente
habrá que calcularlos para una vuelta (son cíclicos), sería
tontería irlos calculando una y otra vez a cada vuelta que
hiciera el enemigo. Por eso es más inteligente calcularlos la
primera vez y almacenarlos en un par de tablas, que serán las
que a partir de entonces definirán el movimiento del enemigo.
Program truko5;
Global
int idenemigo;
int i;
float radio;
float angulo=0;
float camino_x[35];
float camino_y[35];
end
Begi n
set_mode(320,240,16);
464
//Se muestra en pantalla el enemigo
idenemigo=load_png("enemigo.png");
map_clear(0,0,rgb(20,50,80));
enemigo(160,100);
write(0,0,230,0,"Press ESC to exit");
Repeat
Frame;
Until (key(_esc))
End
Process enemigo(x,y)
Private
int centrox;
int centroy;
End
Begin
file=0;
graph=idenemigo;
centrox=x;
centroy=y;
Loop
For(i=0;i<=35;i=i+1)
//Las coordenadas hacen referencia al centro del círculo
x=centrox+camino_x[i];
y=centroy+camino_y[i];
Frame;
End
End
End
465
Cómo grabar partidas y reproducirlas posteriormente
(extraido del tutorial de Pescado, http://www.bertesh.tk)
Program mi_juego;
global
int mifichero_fpg;
end
Begin
set_mode(800 ,600,16);
mifichero_fpg=load_fpg("fichero.fpg");
write(0,400,290,4,"1=JUGAR");
write(0,400,300,4,"2=VER DEMO");
write(0,400,310,4,"3=SALIR");
loop
if(key(_1))
delete_text(0);
empieza_partida();
break;
end
if(key(_2))
delete_text(0);
ver_una_partida();
break;
end
if(key(_3))
break;
exit("");
end
frame;
end
end
Process empieza_partida()
BEGIN
jugador1();
jugador2();
loop
466
write(0,400,30,4,"PULSA ESC PARA SALIR");
if(key(_esc))
let_me_alone();
breaK;
exit("");
end
frame;
delete_Text(0);
end
end
Process jugador1()
BEGIN
file=mifichero_fpg;
graph=1;
loop
if(key(_up)) y=y-2; end
if(key(_down)) y=y+2; end
if(key(_left)) x=x-2; end
if(key(_right)) x=x+2; end
frame;
end
end
Process jugador2()
BEGIN
file=mifichero_fpg;
graph=2;
loop
if(key(_w)) y= y-2; end
if(key(_s)) y= y+2; end
if(key(_a)) x= x-2; end
if(key(_d)) x+=2; end
frame;
end
end
467
Ahora vamos a grabar una partida . Necesitaremos una
estructura con unos campos que contengan las variables X e Y
de los jugadores (para ir guardándolas cada frame). Así que
en la sección de declaraciones de variables globales escribe:
struct rec
int pos_jug1x[4000];
int pos_jug1y[4000];
int pos_jug2x[4000];
int pos_jug2y[4000];
end
Int var_aux=0;
Var_aux=var_aux+1;
rec.pos_jug1x[var_aux]=x;
rec.pos_jug1y[var_aux]=y;
468
Es decir, suponiendo que el jugador 1 se encuentra en la
posicion x=300 y pulsa la tecla para ir a la derecha, el
valor de la variable rec.pos_jug1x[var_aux], cuando var_aux
sea igual a 0, esta sera igual a rec.pos_jug1x[var_aux]=300;
Cuando var_aux=1 (en el siguiente frame), tendremos
rec.pos_jug1x[var_aux]=302, cuando var_aux valga 2 (en el
siguiente frame), tendremos rec.pos_jug1x[var_aux]=304; y asi,
sucesivamente, iremos guardando las coordenadas de jugador 1.
Save(“partida.dem”,rec);
469
Llamaremos a estos procesos “rec_jugador1()” y
“rec_jugador2()”, y su código (junto con el código
modificador de “ver_una_partida()”) es éste:
Process ver_una_partida()
BEGIN
load("PARTIDA.dem",rec); //cargamos el archivo
rec_jugador1();
rec_jugador2();
loop
write(0,400,30,4,"PULSA ESC PARA SALIR");
if(key(_esc))
let_me_alone();
breaK;
exit("");
end
var_aux=var_aux+1;
if(var_aux>=4000) var_aux=4000; end
frame;
delete_text(0) ;
end
end
Process rec_jugador1()
BEGIN
file=mifichero_fpg;
graph=1;
loop
//X TENDRA EL VALOR DE LA VARIABLE POS_JUG1X EN LA POSICION VAR_AUX
x=rec.pos_jug1x[var_aux];
//Y TENDRA EL VALOR DE LA VARIABLE POS_JUG1Y EN LA POSICION VAR_AUX
y=rec.pos_jug1y[var_aux];
frame;
end
end
Process rec_jugador2()
BEGIN
file=mifichero_fpg;
graph=2;
loop
x=rec.pos_jug2x[var_aux];
y=rec.pos_jug2y[var_aux];
frame;
end
end
470
Ejemplo de creación de un mapa tileado
Program EJEMPLO_MAPEADOR_TILE;
Global
Int Fpg_System;
Int Mapa_negro;
471
1,1,2,10,11,3,3,3,3,3,3,3,9,3,8,5,6,6,7,9,
1,1,2,10,11,5,6,6,6,7,9,3,9,3,8,5,6,6,7,9,
3,3,3,10,11,5,6,6,6,7,9,3,9,3,8,3,10,11,3,3,
3,3,3,10,11,5,6,6,6,7,9,3,9,3,8,3,10,11,3,3,
3,3,3,10,11,5,6,6,6,7,9,3,9,3,8,3,10,11,3,3,
3,3,3,10,11,5,6,6,6,7,9,3,9,3,8,9,3,9,3,9,
3,3,3,10,11,9,9,3,3,3,9,3,9,3,8,3,3,3,3,3,
3,3,3,10,11,9,9,3,3,3,9,3,9,3,8,3,3,3,3,3,
3,3,3,10,11,9,9,3,3,3,9,3,9,3,8,3,3,3,3,3;
Begin
set_mode(640,480,16);
Fpg_system=load_fpg("system.fpg");
Mapeado_tile();
Nave();
Loop
Frame;
If(key(_esc)) exit("");End
End
End
Process nave()
Begin
start_scroll(1,Fpg_system,100,0,0,3);
scroll.camera=id;
ctype=c_scroll;
x=100;
y=200;
graph=200;
loop
If (key(_right)) x=x+2; End
If (key(_left)) x=x-2; End
If (key(_down)) y=y+2; End
If (key(_up)) y=y-2; End
frame;
end
end
472
/*Éste es el proceso que se encarga de crear el mapa tileado*/
Process Mapeado_tile()
Private
//Estas dos variables las defino para referirme a las columnas y
filas de la tabla que tenemos definida en global
int Fila;
int Columna;
//Region_act y ultima_region son las que controlan cuando deben
actualizarse los tiles en el scroll.
Int region_act;
Int ultima_region;
End
Begin
/*Inicialmente ponemos los tiles de la pantalla.Repasa el
funcionamiento de la función map_put si no te acuerdas de él*/
From fila= 0 To 8
From columna= 0 To 11
map_put(Fpg_system,100,Tiles[(fila*20)+columna],columna*64,fi
la*64);
End
End
Loop
//y si avanzamos actualizamos los tiles
If (region_act<>ultima_region)
From fila= 0 To 8
columna=region_act+11;
map_put(Fpg_system,100,Tiles[(fila*20)+columna ],(region_act-
1)*64,fila*64);
End
ultima_Region=region_act;
End
Frame;
End
End
473
servía para insertar una imagen –el tile en este caso- dentro
de otra –el fondo transparente que scrollea-).
474
Mientras no logremos ejecutar el combo, en pantalla
aparecerá una bola que se moverá con los cursores. También
aparece en pantalla un marcador que enseña si se ha pulsado
alguna de las teclas que forman el combo –aparece el código
numérico de la tecla concreta: por ejemplo, la tecla _up se
corresponde con el número 72-. Si se logra conseguir un
combo, verás que en el marcador, evidentemente, aparecerán
que todas las teclas del combo han sido pulsadas, y se
mostrará el efecto del combo, que es rebentar la bola en
bolitas pequeñas que salen disparadas por todos los ángulos.
Program combo;
Global
int tiempo; //guarda el tiempo entre teclas
int cola[6]; //aqui se guardan las teclas
int
combopatron[6]=_up,_down,_up,_down,_right,_right,_down;
// el patron de teclas para el combo
int comboflag=0; // cuando valga 1, habrá combo
End
Local
Int n;
End
Begin
load_fpg("wecombo.fpg");
define_region(1,0,0,320,200);
While(fading) Frame; End;
write(0,20,10,0,"arr, aba, arr, aba, der, der, aba");
For(n=0;n<7;++n)
write_var(0,20+n*30,30,0,cola[n]);
End
juego();
While(get_id(Type juego));
Frame;
End
475
delete_text(0);
let_me_alone();
unload_fpg(0);
End
Process coladato(dato)
Begin
For(n=0;n<6;++n)//muevo los datos adelante para hacer un hueco
cola[n]=cola[n+1];
End
cola[6]=dato; // meto el dato nuevo
For(n=0;n<7;++n) // comparo
If(cola[n]!=combopatron[n]) Return(0); End /*había
alguno distinto*/
End
comboflag=1; //Combo!!
End
Process juego()
Private
Int arriba, abajo, derecha, izquierda;
end
Begin
graph=1;
x=160;
y=100;
Frame;
Loop
If(key(_esc)) break; End
476
If(key(_up) && y>20)
y=y-3;
If(!arriba)
controltiempo();
If(coladato(_up)) haz_combo(x,y); End
arriba=1;
End
End
If(!key(_up)) arriba=0; End
Frame;
End
End
477
Begin
For(n=0;n<10;n=n+1)
bolita(x,y,rand(0,360));
End
comboflag=0;
End
/*Muestra el efecto visual del combo, que es que las bolitas salgan
disparadas en direcciones diferentes*/
Process bolita(x,y,angle)
Begin
angle=angle*1000;
graph=1;
size=50;
While(!out_region(id,1))
advance(5);
Frame;
End
End
478
predefinida por Fénix para ayudarnos a no tener que
acordarnos de cuál era el código de la tecla “a”/”A”. Y aquí
está la clave de todo el misterio.
479
la tecla, pero si intentas dar ese salto que te va a llevar a
la salvación y el ordenador se niega a responder a nuestra
órdenes con presteza... Creo que me entendéis.
Program Redefinir_teclas;
Global
Int derecha;
Int izquierda;
end
Begin
set_mode (320,240,16);
// Se define la tecla para ir a la derecha
write(0,0,0,0,"Pulse tecla para derecha");
Repeat
derecha=scan_code;
Frame;
Until (derecha<>0)
delete_text(0);
write(0,0,0,0,"Pulse esc para terminar");
Loop
// Se mueve el gráfico
If (key(derecha)) x++; End
If (key(izquierda)) x --; End
480
Crear explosiones un poco más espectaculares:
Program ejemplo_expl;
Global
Int fichero1;
end
Begin
set_mode(640,480,16);
fichero1=load_fpg("explos.fpg");
Loop
If(key(_esc)) exit("");End
explosion(260,220,size);
Frame;
End
End
Process explosion(x,y,size)
Begin
graph=331;
Repeat
set_fps(24,0);
If (size>50 AND rand(0,40)<size/8) /* Crea otras
explosiones*/
x=x+rand( -25,45);
y=y+rand( -30,15);
End
size=size-4; // Cambian de tamaño reduciendose
Frame;
Until (size<5) // Continua hasta que sean demasiado pequeñas
End
481
Ejemplo de uso del parámetro de la orden Frame:
Program ejemplo_avisos;
Global
int fuente3;
int nivel_actual;
end
Local
Int info=1;
end
Begin
set_mode(640,480);
titulo_nivel(info);
End
Process nivel()
Begin
info=nivel_actual;
nivel_actual=nivel_actual+1;
482
Frame (8000); //pausa
titulo_nivel(nivel_actual+1);
write(0,50,50,0,"CUANDO TE CANSES DE AVISOS PULSA Esc");
Frame;
End
Program RoundGrav;
// globales
// Grab:Indice de gravedad (1)
// MP:mapa creado con newmap para los scroll
Global
int grav=1;
int MP;
end
// Locales:
// Jump:Indica el cambio en el salto
Local
int Jump=0;
end
Begin
set_mode(640,480,16,MODE_FULLSCREEN);
write(0, 160, 50, 4, "Teclas [Right, Left, Space]");
load_fpg("Test.fpg");
MP=new_map(2000,800,8);
mouse.graph=101;
mouse.flags=4;
483
scroll.camera=Bola(20,350);
map_put(0,mp,200,1000+(cos(x*180)*320),400+(sin(x*180)*3
10));
If (key(_esc)) exit();End
Frame;
End
End
Process Bola(x,y)
// VEL:velocidad lateral
// Sue --> 0 no toca con nada
// 1 Toca con el suelo (si cae)
// 2 Toca con el techo (si sube)
484
Private
int vel;
int sue;
end
Begin
graph=100;
Loop
// Inercia a la izq.
If (key(_left)) vel=vel+1;End;
// Y a la derecha
If (key(_right)) vel=vel-1;End;
// se rota dependiendo de la velocidad lateral
angle=angle+vel*1000;
Process Suelo(T,An)
// Toy: nueva y
Private
int toy;
485
end
Begin
// se actualiza el salto teniendo en cuenta la gravedad
t.jump=t.jump-grav;
// si se baja
If (toy>t.y)
// empieza por encima para subir segun el terreno
T.y-=9;
// mientras no se consiga la nueva posicion
While (toy>t.y)
// se baja 1
t.y=t.y+1;
/ / si ya hay suelo sal con codigo 1
If (map_get_pixel(0,mp,t.x,t.y+an)<>0)
t.jump=0;
Return(1);
End
End
Else // si se sube
// el techo tb puede ser irregular
t.y=t.y+9;
// igual pero al contrario
While (toy<t.y)
t.y=t.y-1;
If (map_get_pixel(0,mp,t.x,t.y-an)<>0)
t.jump=-t.jump;
Return(2);
End
End
End
End
486
Forzar a los procesos a realizar trayectorias concretas:
PATH_FIND (INT fichero, INT gráfico, INT x1, INT y1, INT
x2, INT y2, INT opciones)
487
o 0 si no es posible la comunicación entre los puntos de
inicio y fin. Para ir obteniendo sucesivamente los puntos
intermedios de que consta el camino es preciso emplear la
función path_getxy().
PARAMETROS:
PARAMETROS:
488
de memoria se le pasan, con el contenido del siguiente punto
del camino encontrado por la última llamada válida a
path_find. La función devuelve 0 si no quedan mas puntos en
el camino, y en ese caso, no modifica las variables. Los
puntos de inicio y final se encuentran entre los devueltos
pos la funcion. La primera llamada actualizará x e y con los
valores del punto de inicio del camino.
PARAMETROS:
489
program PATH_FINDING;
global
int mapa;
int i
int j;
int p;
int o;
int puntos;
end
begin
set_mode(400,300,16);
set_fps(40,0);
490
end
delete_text(0);
write(0,200,280,4,"El proceso ha llegado al destino.");
write(0,200,290,4,"Pulsa una tecla para salir...");
while(!scan_code) frame; end
exit();
end
process prota(x,y)
begin
z=-1;
graph = new_map(8,8,16);
drawing_map(0,graph);
drawing_color(rgb(255,255,0));
draw_fcircle(3,3,3);
loop
frame;
end
end
process objetivo(x,y)
begin
z=0;
graph=new_map(16,16,16);
drawing_map(0,graph);
drawing_color(rgb(0,255,255));
draw_box(0,0,15,15);
loop
frame;
end
end
491
código en cada proceso, con todo lo que esto conlleva:
posibles errores al duplicar el código, tener que cambiar
todos los procesos afectados si se hace algún cambio, código
más largo y menos legible,...
492
Otra posibilidad es dar órdenes a varios individuos a la
vez. Se pueden guardar sus posiciones en la estructura en un
pequeño array e ir variando a la vez todos las posiciones de
la tabla que hagan falta. Así nos dará igual si los
individuos que hemos elegido son del mismo tipo, de distinto
tipo, si se pueden mover o no, todos mirarán las órdenes y su
estado y actuarán en consecuencia. Todo esto unido a que
cualquier proceso puede acceder en cualquier momento a estos
valores para realizar cualquier tipo de comprobación o ajuste
lo convierten, en mi opinión, en un buen sistema.
493
llegado todavía al que tenía que ir (se podría haber
implementado un sistema de puntos de ruta). Si mientras se
está moviendo seleccionamos otro individuo le podremos dar
órdenes y se moverá, pero sin afectar al movimiento del
individuo original, que irá hasta donde tiene que ir. No se
ha implementado la posibilidad de elección múltiple para no
complicar el programa. Para que funcione deberás tener dos
imágenes PNG llamadas “bicho.png” y “raton.png”, que
representarán, respectivamente a los individuos y al puntero
del ratón.
PROGRAM ejemplo_manejo_individuos;
GLOBAL
Int grafico; // Gráfico de los bichos
Int seleccionado; // Qué individuo hemos seleccionado
struct bichos[3]; //4 posiciones para 4 bichos(empezando de 0)
int x_dest;
int y_dest; // Posición a la que se tienen que mover
END
END
BEGIN
set_mode(640,480,16);
LOOP
// Se selecciona el individuo correspondiente
IF (key(_1)) seleccionado=0; END
IF (key(_2)) seleccionado=1; END
IF (key(_3)) seleccionado=2; END
IF (key(_4)) seleccionado=3; END
494
// Se pone el destino al pinchar con el ratón
IF (mouse.left)
bichos[seleccionado].x_dest=mouse.x;
bichos[seleccionado].y_dest= mouse.y;
END
FRAME;
END
END
program tutorial;
global
int score[1];
//Gráficos
int gr_ball;
int gr_bat;
//Teclas
int __A = _control;
495
int __B = _alt;
int __SELECT = _space;
int __START = _enter;
int __R = _tab;
int __L = _backspace;
end
begin
set_mode(320 ,240,16);
gr_bat = new_map(15,40,8);
map_clear(0,gr_bat,13);
loop
if(key(__START)) exit(); end
frame;
end
end
Process ball()
private
//Velocidad de la bola
int speedo;
begin
496
x = 160;
y = 120;
angle = rand(0,360) * 1000;
speedo = 500;
graph = gr_ball;
loop
/*Makes it bounce off the top and bottom of the screen(0 and
240, but giving the ball a 5 pixel radius)*/
if(y<5 or y>235)
//Incoming angle is outgoing angle!
angle = -angle;
end
497
Process bat(x,keyup,keydown)
begin
y = 120;
graph = gr_bat;
loop
if(key(keyup)) y =y-12; end
if(key(keydown))y =y+12; end
498
¿Y por qué este funcionamiento del FXC? Por una política
de modularización. Es decir, se ha elegido la opción de tener
un compilador pequeño, que reconozca las mínimas funciones
posibles, pero optimizado y rápido, en vez de tener un
megacompilador con soporte para muchas funciones pero en el
que su rendimiento se podría ver comprometido en términos de
velocidad y eficacia.
499
nosotros no veremos, y otras, como “gui.dll”,
“ttf.dll”,pueden aparecer o no dependiendo de la versión de
Fenix. Ahora nosotros lo que veremos es cómo utilizar la dll
oficial “mpeg.dll”.
Import “ruta_de_la_dll”;
500
referencia explícita a dicho archivo en nuestro código con
import: bastará con hacerlo a “mpeg.dll” solamente.
LOAD_MPEG(“fichero”,video,audio)
PARÁMETROS
DESCRIPCIÓN
PLAY_MPEG(video)
PARÁMETROS
DESCRIPCIÓN
501
Esta función empieza a reproducir un vídeo MPEG pero
¡OJO!, el vídeo no se muestra en pantalla automáticamente. Lo
que hace exactamente esta función es devolver el código de un
gráfico perteneciente a la librería 0 (la del sistema: FILE=0)
que representa en todo momento el frame actual del video.
Para mostrar el video en pantalla, la forma recomendada es
asignar el código devuelto por PLAY_MPEG a la variable GRAPH
de un proceso. Las coordenadas X e Y del proceso
identificarán entonces la posición del video en pantalla, a
partir de su centro.
STOP_MPEG(video)
PARÁMETROS
DESCRIPCIÓN
PAUSE_MPEG(video)
502
INT VÍDEO : Identificador del vídeo
DESCRIPCIÓN
IS_PLAYING_MPEG(video)
PARÁMETROS
DESCRIPCIÓN
MPEG_CURRENT_TIME(video)
PARÁMETROS
DESCRIPCIÓN
MPEG_TOTAL_TIME(video)
503
PARÁMETROS
DESCRIPCIÓN
REWIND_MPEG(video)
PARÁMETROS
DESCRIPCIÓN
LOOP_MPEG(video, loop)
PARÁMETROS
DESCRIPCIÓN
504
SET_MPEG_VOLUME(video,filter)
PARÁMETROS
DESCRIPCIÓN
UNLOAD_MPEG(video)
PARÁMETROS
DESCRIPCIÓN
Program MPEG;
import "mpeg.dll" //importamos la dll
505
global
Int video; //variable que usaremos para identificar el video
Int tiempo_total; /*variable para controlar el tiempo total que
dura un video*/
Int tiempo_actual; /*variable para controlar el tiempo
actual de un video*/
String texto; //variable para controlar el texto escrito
end
begin
video=load_mpeg("goal.mpg",1,1);
tiempo_total=mpeg_total_time(video);
tiempo_actual=0;
loop_mpeg(video,1);
506
// mostramos los textos por pantalla
repeat
tiempo_actual=mpeg_current_time(video);
delete_text(texto);
texto= write (0,0,20,0,"TIEMPO ACTUAL:
"+itoa(tiempo_actual));
if (key(_esc))
loop_mpeg(video,0);
end
frame;
until (is_playing_mpeg(video)==false)
stop_mpeg(video);
unload_mpeg(video);
End
507
Introducción a la librería oficial GUI.DLL:
508
momento de este enlace:
http://fenix.divsite.net/download/gui.dll
509
la “gui.dll” apta (más o menos) para la versión Fénix 0.84b
(la descargada del enlace escrito más arriba), para que este
binomio de archivos pueda trabajar a pleno rendimiento.
Program hola;
/*#define LANG ES
include "gui.inc";*/
import "gui.dll";
global
Int idventana;
Int idboton;
int idboton2;
int idcheckbox;
int varcheckbox;
int iddropdown;
string vardropdown;
string valoresdropdown[4]="ab","cd","ef","gh","ij";
int idhscrollbar;
int idvscrollbar;
pointer poshscrollbar;
pointer posvscrollbar;
int idinputline;
char varinputline[50];
510
int idlabel;
int idradio;
int idradio2;
pointer varradio;
int idtable;
int idtaskbar;
end
begin
//El programa ha de funcionar en 16 bits
set_mode(640,480,16);
//idfpg=load_fpg("nuevo.fpg"); No funciona!!!
511
/*Incluimos todos los controles creados previamente en memoria
dentro de una ventana -contenedor- determinada.Éste es un paso previo
imprescindible para poder asociar un control determinado a una ventana
determinada y hacer entonces que cuando se visualice la ventana, se
visualicen con ella todos (y solamente) us controles asociados también.*/
Window_addcontrol(idventana,50 /*Pos X del control dentro de
la ventana*/,50 /*Pos Y del control dentro de la ventana*/,idboton);
window_addcontrol(idventana,150,50,idboton2);
window_addcontrol(idventana,250,50,idcheckbox);
window_addcontrol(idventana,450,50,iddropdown);
window_addcontrol(idventana,250,250,idhscrollbar);
window_addcontrol(idventana,1,1,idvscrollbar);
//window_addcontrol(idventana,50,50,idfpg);
window_addcontrol(idventana,50,100,idinputline);
window_addcontrol(idventana,150,100,idlabel);
window_addcontrol(idventana,400,100,idradio);
window_addcontrol(idventana,500,100,idradio2);
window_addcontrol(idventana,50,150,idtable);
table_width(idtable,"" /*Columna a cambiar de tamaño, o cadena
vacía si es toda la tabla*/,50 /*ancho*/ );
table_select(idtable,1 /*indice de la fila seleccionada,
empezando por 0*/ );
window_addcontrol(idventana,600,400,idtaskbar);
control_focus(idlabel);
512
//write(0,10,10,4,"Muy bien"); No funciona!!!!
label_text(idlabel,"Botón 2 pulsado");
end
if (varcheckbox== true)
label_text(idlabel,"Checkbox chequeado");
end
if(control_changed(idinputline)==true)
label_text(idlabel,"Has cambiado el texto del
inputline");
end
if(control_changed(idtable)==true)
//label_text(idlabel,"Has cambiado algún valor de
alguna fila de la tabla");
label_text(idlabel,"Has cambiado algo en la tabla
y ahora la fila seleccionada es"+ table_selected(idtable));
end
if(control_changed(idcheckbox)==true)
label_text(idlabel,"Has chequeado/deschequeado un
checkbox");
end
if(control_pressed(idlabel,1))
label_text(idlabel,"Has hecho clic sobre el
label");
end
if(control_doubleclick(idlabel) )
label_text(idlabel,"Has hecho doble clic sobre el
label");
end
if(c ontrol_focused(idlabel))
label_text(idlabel,"La etiqueta tiene el foco");
end
if(control_leaved(idlabel))
label_text(idlabel,"La etiqueta pierde elfoco");
end
Frame;
End
end
513
Estas librerías no son “oficiales” porque no se
distribuyen con el paquete de Fénix disponible en la web.
Para que una DLL sea considerada oficial ha de cumplir varios
requisitos: que sea libre, que sea de interés general para la
mayoría de programadores de Fénix y que sea multiplataforma
(es decir, que no se ciña solamente a un sistema operativo
concreto y que por tanto funcione igual en Windows que en
Linux, por ejemplo), ya que Fénix por definición pretende ser
un lenguaje interpretado multiplataforma.
Tcpsock.dll (http://www.infonegocio.com/hsoft/fenix):
Alternativa a la anterior, que suple algunas
carencias de aquélla.Por ejemplo, la net.dll tiene
entre otros problemas el de no poder conectar más
de un cliente a un servidor (sólo es útil para
“player vs. Player”), aspecto en el que la
tcpsock.dll no tiene esta limitación.No obstante,
la tcpsock.dll sólo soporta el protocolo TCP.
LibDPlay.dll (http://www.flamingbird.com o
http://coldev.cjb.net o
http://fenixworld.se32.com/download.php?list.8):
Otra alternativa más para incluir funcionalidad
multijugador para juegos en red. Soporta UDP y TCP.
514
LibOdbc.dll (http://www.flamingbird.com o
http://coldev.cjb.net o
http://fenixworld.se32.com/download.php?list.8):
Librería necesaria para conectar con servidores de
bases de datos (MySQL, MSSQLServer, Oracle,
Access,etc). Útil para poder guardar y recuperar de
una base de datos información como puntuaciones,
récords, usuarios y contraseñas,etc.
LibClipboard.dll (http://www.flamingbird.com o
http://coldev.cjb.net o
http://fenixworld.se32.com/download.php?list.8):
Librería necesaria para poder enviar datos al
portapapeles y recuperarlos de allí. Sólo funciona
en Windows.
LibRegistry.dll ( http://www.flamingbird.com o
http://coldev.cjb.net):
Librería necesaria para poder leer y modificar el
regi stro de Windows. Útil para poder guardar y
recuperar datos permanentes de configuración
en un juego, por ejemplo.Sólo funciona en Windows.
ETD.dll (http://www.flamingbird.com o
http://fenixworld.se32.com/download.php?view.9):
Librería que añade funcionalidad 3D básica al
lenguaje Fénix.
Lib3Dm8e.dll (http://coldev.cjb.net,
http://www.flamingbird.com o
http://fenixworld.se32.com/download.php?list.8):
Alternativa a la anterior.
515
no olvides, que al ser libres, podrás disponer del código
fuente de las librerías.
516
Epílogo
(extraído de la carta abierta de Emilio de Paz, http://www.alcachofasoft.com)
517
Pero hay algo que ningún otro medio tiene, al menos en
tan gran medida: la INTERACCIÓN. Y es que en aquellos
videojuegos, sin importar el tipo de historia, o el género
del juego... NOSOTROS éramos el protagonista.
518
Y nuestra profesión por tanto, de programadores,
diseñadores, músicos, grafistas, directores... de ARTISTAS al
fin y al cabo, es transmitir esas emociones.
519
ÍNDICE
Agradecimientos.....................................1
A quién está dirigido este texto....................1
520
CAPITULO 1: PRIMEROS PASOS CON FENIX
521
Bloque “From/End”........................129
Algunos ejemplos más de códigos fuente...130
Concepto de proceso..........................155
Las variables locales predefinidas
FILE,GRAPH,X e Y........................157
Creación de un proceso.......................159
Parámetros de un proceso.....................163
Creación de una función......................165
Variables globales,locales y privadas........170
Constantes...................................174
Tablas.......................................176
Estructuras..................................177
Imágenes y transparencias....................181
El juego básico. Collision().................183
Añadiendo giros al movimiento del personaje
Adva nce() y ANGLE.......................185
Inclusión de múltiples enemigos diferentes
SIZE....................................186
Añadiendo explosiones........................190
Añadiendo los disparos de nuestro personaje..193
Códigos identificadores y señales. ID........196
La familia de los procesos
FATHER,SON,BIGBRO y SMALLBRO............198
Añadiendo sonido.............................201
Añadiendo música.............................204
522
CAPITULO 6: TUTORIAL PARA UN MATAMARCIANOS
Punto de partida.............................207
Añadiendo los disparos de nuestra nave.......209
Añadiendo los enemigos.......................214
Eliminando procesos,matando enemigos.........219
Añadiendo explosiones........................224
Añadiendo energía enemiga....................226
Añadiendo energía nuestra....................228
Disparo mejorado y disparo más retardado.....234
Disparos de los enemigos.....................238
Las fuentes. La aplicación “FNT Edit”........239
Añadiendo los puntos.........................241
Usando el ratón..............................245
Añadiendo scrolls de fondo...................247
La pantalla de inicio........................252
Creando el mundo y nuestro
Protagonista moviéndose en él...........259
El mapa de durezas...........................266
Entrar y salir de la casa....................275
Conversaciones entre personajes..............281
Entrar y salir de la segunda casa............294
La pantalla del inventario...................298
Guardar partidas en el disco duro
y cargarlas posteriormente..............313
Añadiendo enemigos, medida de vida y GameOver316
Concepto y utilidad de los “tiles”...........323
Includes.....................................325
Antes de empezar.............................349
Representación del área del juego............350
Representación de las figuras................351
523
Evolución del juego..........................353
Colisiones entre límites del área de juego...355
Colisiones entre piezas......................357
Cómo averiguar si hemos hecho linea..........359
En resumen...................................361
EPILOGO...........................................517
524
525