Probemos un proyecto para administrar algunos JSL
Hace mucho tiempo sugerí escribir un desplazamiento lateral https://community.jmp.com/t5/Uncharted/Wild-Road-Map/bc-p/20996/highlight/true#M5 . Si no estabas jugando en los años 80, https://en.wikipedia.org/wiki/Parallax_scrolling tiene algunos ejemplos. Los juegos de desplazamiento lateral son juegos en 2D en los que un personaje se mueve de izquierda a derecha y el fondo puede tener varias capas que se deslizan a diferentes velocidades para dar una sensación de profundidad en 3D. Otras variaciones pueden ser puentes de plataforma con una dimensión de desplazamiento vertical también.
Hay cuestiones por resolver:
- desarrollo de juegos (¿puede JMP hacer esto más fácil de escribir?)
- gráficos del juego (¿cómo se ve?)
- audio del juego (¿escuchaste eso?)
- entrada del juego (¿cómo me muevo?)
- juego (¿pasa algo?)
1. Desarrollo de juegos
Estoy usando un proyecto JMP (y recientemente, cambié a un proyecto autónomo JMP 16). Me está empezando a gustar el proyecto autónomo para un proyecto que tiene de cinco a diez archivos independientes. Tiene pestañas para los archivos abiertos y puntos de acoplamiento para la ventana de registro. El proyecto autónomo almacena todo en un archivo (zip, debajo de las cubiertas) y desempaqueta y vuelve a empaquetar automáticamente a medida que se abre y se cierra.
Lograr que este juego funcionara fue más fácil porque el archivo de gráficos, el archivo de audio y el archivo de juego son archivos independientes y relativamente pequeños. Eso hace que sea más fácil encontrar cosas, como el JSL que administra el juego entre los personajes del juego. El proyecto muestra los archivos en pestañas, así:
A JMP project with several files.Un proyecto JMP con varios archivos.
Hay nueve archivos abiertos tan pronto como se abre este proyecto; el archivo que contiene la definición de clase para vampiros en el juego está actualmente a la vista. No me siento completamente cómodo cuando los archivos se guardan nuevamente en el proyecto y cuando el proyecto ya se guardó, así que uso los íconos resaltados en la parte superior izquierda antes de cerrar el proyecto. También acoplé el registro en la parte inferior izquierda. Los proyectos parecen estar aislados del resto de JMP y entre sí, por lo que tienen su propio registro.
El archivo MakeAddin no es parte del juego, pero es parte del desarrollo del juego. Si esa pestaña está abierta y la ^R (ejecutar), volverá a generar el complemento. ¿Qué sucede si ejecuto otra pestaña? Ese JSL se ejecuta, lo que podría no ser el comienzo de la lógica del juego. Pero lo hice todo el tiempo, por error, así que lo arreglé así:
- Hay un archivo principal, game.jsl, que incluye todos los demás archivos en un orden razonable.
- La mayoría de los archivos son independientes y solo crean una clase o un espacio de nombres.
- El archivo sidescroller.jsl necesita cargar todos los demás archivos (es el bucle principal del juego).
Los archivos independientes todos final con
try(include("game.jsl"),if(!contains(char(exception_msg),"includes itself through a closed path"),show(exception_msg)));que ejecutará el archivo game.jsl después del archivo actual. El archivo game.jsl intentará volver a incluir el archivo actual y arrojará un error de inclusión recursivo, que se ignora, o algún otro error, que se informa. El archivo de desplazamiento lateral, que debe ser el último, tiene esa declaración de prueba como el primero línea. De esa forma, el archivo game.jsl incluye todo lo demás antes de que se ejecute el resto del archivo de desplazamiento lateral.
El archivo game.jsl se ve así
files = {"AUDIO.jsl", "RGBA.jsl", "ASSETS.jsl", "LAYERS.jsl", "VAMPIRE.jsl", "BEAR.jsl",
// currently SIDESCROLLER contains the game logic. All of the files include this file,
// at the end, except for SIDESCROLLER which include it at the start. SIDESCROLLER is
// dependent on all of the other files being loaded. The other files are not order-dependent.
"SIDESCROLLER.jsl"};
For( iFile = 1, iFile <= N Items( files ), iFile += 1,
Try(
Include( files[iFile] ), // this exception is EXPECTED if you start one of the other files; it is OK!
If( !Contains( Char( exception_msg ), "includes itself through a closed path" ),
Show( files[iFile], exception_msg )
)
)
);Ahora, cualquier archivo que se edite se puede ejecutar. Excepto... si se edita más de un archivo, ¡asegúrese de guardar los que no se ejecutan explícitamente! La inclusión obtiene la copia del disco, no la copia de la pestaña de edición. Pero si solo cambia un archivo, debido a la forma en que las inclusiones están configuradas arriba, usará la copia de la pestaña de edición actual sin necesidad de guardar.
Lista de deseos:
- guarde todo antes de ejecutar (o haga que incluya use la copia de la pestaña de edición)
- especificar un archivo de inicio al ejecutar un proyecto (no el archivo de carga del proyecto)
- documentar cómo guardar una pestaña y guardar el proyecto son iguales/diferentes
- una forma de ejecutar el comando guardar todo del proyecto desde JSL
2. Gráficos del juego
Este proyecto utiliza gráficos generados por JSL y gráficos de mapa de bits descargados. A continuación, JSL crea el cielo azul, las montañas, los árboles y el camino de grava. La cara de la pared de roca es un mapa de bits en mosaico y los vampiros, los murciélagos y el oso son secuencias animadas de mapas de bits. Créditos al final.
The little bear is idling; the vampires are releasing bats or disappearing.El osito está holgazaneando; los vampiros están soltando murciélagos o desapareciendo.
El archivo RGBA.jsl es una parte importante de los gráficos JSL. JMP puede dibujar con transparencia en un gráfico y puede usar mapas de bits con transparencia en un gráfico, pero no hay una manera fácil de, por ejemplo, hacer un gráfico de líneas en un fondo transparente y tomar ese mapa de bits con el fondo transparente. (Hay una opción, Fondo transparente para imágenes PNG de informes, que guarda todo excepto el gráfico con transparencia. Puede editar un SVG guardado para eliminar el fondo, pero necesita otro programa para volver a convertirlo en un PNG en un fondo transparente).
Lista de deseos
El archivo RGBA crea una clase para crear mapas de bits con píxeles transparentes donde no se dibuja nada. Es posible que necesite extenderse para sus primitivas favoritas. Echa un vistazo a los árboles con hojas no muy opacas y espacios entre las hojas. Árbol Bezier usando Path() describe dibujar los árboles, pero no con el canal alfa. Las montañas son varias capas de un algoritmo de dibujo de colinas con diferentes parámetros, y el cielo es un rectángulo sombreado de azul a negro no transparente. El camino de grava puede ser bastante lento de generar, pero lo suficientemente rápido una vez que se ejecuta; cada trozo de grava se dibuja en un mapa de bits.
El archivo ASSETS.jsl conoce los activos de mapa de bits que usa el juego, cómo abrirlos, cómo normalizarlos y cómo nombrarlos. Debido a que las animaciones son dibujadas por diferentes personas, requieren normalización para centrarlas de la misma manera. Esa información se mantiene en la estructura como esta
Assets:vampire_images["hit"] = {{"attack/hit_1",.,.,0.80,0.5},que muestra que los vampiros lanzan murciélagos se desplaza (.80 frente a .5 en otros) en el mapa de bits para manejar la animación extra ancha. Los vampiros y el oso obtienen sus propias capas en el desplazamiento lateral y su transparencia permite que se vean capas más distantes.
Debido a que el juego en realidad está en un gráfico, puedes echarle un vistazo con la herramienta de zoom para comprender las capas. Aquí está alejado y puede ver que el cielo no es más grande, de izquierda a derecha, que la ventana. No se desplaza. Pero las capas más y más cercanas se vuelven más y más anchas para que puedan desplazarse más.
Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normalmente, el cielo llena la pantalla, de izquierda a derecha. Zoom out, ver el vampiro a punto de entrar en el escenario.
A medida que el osito camina hacia la derecha, las posiciones de los mapas de bits se ajustan...
Not afraid.No tiene miedo.
Justo pasado el medio...
The NPCs animate even when off the display.Los NPC se animan incluso cuando están fuera de la pantalla.
Y en el otro extremo...
The little bear has reached the right-hand end of his world.El osito ha llegado al extremo derecho de su mundo.
Los vampiros caminan más lento que el oso. Se pondrán al día. Y aquí está con algunas de las imágenes de fondo y de primer plano eliminadas...
Without all the layers, the characters seem to float.Sin todas las capas, los personajes parecen flotar.
3. Sonido del juego
El sonido es una parte importante de una experiencia inmersiva. Usé LMMS para hacer los sonidos en este juego, y un diseñador de sonido adecuado podría hacerlo mucho mejor. Pero sigue siendo bastante bueno porque los sonidos pueden superponerse sin cortarse entre sí. En Windows. Probablemente en Mac si alguien con una Mac encuentra un programa de línea de comandos que pueda reproducir un sonido. Gracias @Jeff_Perkinson por ayudarme con un poco de código de audio de Mac. Para Windows, el archivo AUDIO.jsl usa RunProgram para ejecutar PowerShell, que tiene acceso al sistema de sonido de Windows. En Mac, utiliza un reproductor de sonido de línea de comandos, /usr/bin/afplay. Es posible que JMP se bloquee si intenta salir de JMP antes de que finalicen todos los sonidos de reproducción; simplemente deje que la barra de progreso final termine de reproducir todos los sonidos iniciados.
4. Entrada del juego
Este juego utiliza el teclado como dispositivo de entrada colocando un MouseBox alrededor del Graph Box que contiene el juego.
New Window( "overlay",
mb = MouseBox(
V List Box(
hlistbox(
leftSpacer=spacerbox(size(5,windowy),color("black")),
gb = Graph Box(
suppressaxes,
framesize( windowx + 1, windowy + 1 ),
X Scale( -1, windowx ),
Y Scale( -1, windowy )
),El MouseBox reenvía las pulsaciones de teclas a una pequeña función que recuerda la tecla y la hora
lastkey=empty();
lastkeyexpire=tickseconds();
spriteRunner = function({key},{},
lastkey=key;
lastkeyexpire=tickseconds()+1;
);En otros lugares, la acción de la tecla se repite hasta que se vuelve obsoleta un segundo después.
While( go,
if( tickseconds() > lastkeyexpire,
lastkey="";
);
…Las llaves dirigen al osito. Se implementan en la máquina de estados finitos (FSM) del oso. Un ejemplo que cambia el oso de inactivo a caminar está en la función BEAR.jsl cycle():
If( begin,
laststate=m_state;
if( keystroke=="LEFT" & ( pFaceLeft=="faceright" | (pFaceLeft=="faceleft" & pName=="idle") ),
m_state = m_stateWalkLeft;Begin es verdadero si el ciclo de animación actual está al principio, lo que lo convierte en un buen lugar para cambiar de estado. Parece extraño dejar el medio de una animación de salto a una animación inactiva. Ha pasado un tiempo desde que escribí esa lógica que dice que el oso solo debe comenzar a moverse hacia la izquierda si mira hacia la izquierda y está inactivo, pero si mira hacia la derecha... ¿solo debe comenzar a moverse hacia la izquierda? Creo que eso permite una reversión instantánea al caminar y tal vez evita un cambio al mismo estado que también podría solucionarse de otra manera.
5. Juego
Hacer un juego convincente es la parte difícil para mí. Contar una historia, crear una meta, agregar los rompecabezas. Este juego es principalmente un entorno, esperando a un narrador. Pero agregué un poco de juego.
,/*else if*/ keystroke=="DOWN" & m_NumRocks<6 & (m_state == m_stateWalkLeft | m_state==m_stateIdleLeft),
m_state = m_statePickLeft;
m_NumRocks = min(6,m_NumRocks+1);
// use SPACE to HIT
,/*else if*/ keystroke==" " & (m_state == m_stateWalkRight | m_state==m_stateIdleRight),
if(m_NumRocks,
m_NumRocks = max(0,m_NumRocks-1);
m_state = m_stateHitRight;
m_RockThrownRightFrom = m_x;
);Presionando la flecha hacia abajo recoge rocas; el osito puede contener hasta seis. Presionar la barra espaciadora lanza la roca en la dirección de frente. El oso toma nota de la piedra arrojada en RockLanzadoDerechaDesde o RockLanzado Izquierda De que se comprueba mediante código en el bucle principal que puede ver vampiros y el oso.
// check for interaction between bear and vampire
if(!ismissing(bearPlayer:m_RockThrownRightFrom),
if(bearPlayer:m_RockThrownRightFrom<GETPOS></GETPOS>
Golpear a un vampiro con una piedra lo hace retroceder 50 píxeles. (En realidad, puedes obtener dos vampiros de un tiro de la forma en que lo escribí.) Los vampiros probablemente deberían estar en una lista que podría soportar más de dos. En realidad, todos los NPC (personajes que no son jugadores) deben estar en una lista y deben subclasificarse de una clase base común para realizar las llamadas a Cycle () en un bucle en lugar de
vampire1:cycle(stagepos);
vampire2:cycle(stagepos);
bearPlayer:cycle(stagepos,lastkey);
pero eso solo oscurecería cómo funciona este simple ejemplo.
Debe haber dos archivos adjuntos: un archivo de proyecto y un archivo de complemento. (No puedo adjuntar el proyecto a la comunidad en este momento, pero si instala el complemento, hay una copia del proyecto en el directorio del complemento).
Créditos de arte
El sitio abierto de arte del juego tiene mucho más contenido de calidad variable.