Probemos un proyecto para gestionar 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 estuvieras jugando en los años 80, https://en.wikipedia.org/wiki/Parallax_scrolling tiene algunos ejemplos. Los juegos de desplazamiento lateral son juegos 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 tridimensional. Otras variaciones también podrían ser puentes de plataforma con una dimensión de desplazamiento vertical.
Hay cuestiones por resolver:
- desarrollo de juegos (¿puede JMP hacer que esto sea 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?)
- jugabilidad (¿pasa algo?)
1. Desarrollo de juegos
Estoy usando un proyecto JMP (y recientemente estoy cambiando a un proyecto autónomo JMP 16). Me empieza 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, bajo las sábanas) y lo descomprime y vuelve a empaquetar automáticamente a medida que se abre y se cierra.
Hacer 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 separados y relativamente pequeños. Eso hace que sea fácil encontrar cosas, como el JSL que gestiona el juego entre los personajes del juego. El proyecto muestra los archivos en pestañas, como esta:
A JMP project with several files.A JMP project with several files.A JMP project with several files.A JMP project with several files.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 los vampiros en el juego está actualmente a la vista. No me siento completamente cómodo con cuándo se guardan los archivos nuevamente en el proyecto y cuándo se guarda el proyecto todavía, así que uso los íconos resaltados en la parte superior izquierda antes de cerrar el proyecto. También conecté 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 forma parte del juego, pero forma parte del desarrollo del juego. Si esa pestaña está abierta y la ejecuto ^R, se regenerará el complemento. ¿Qué pasa 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 hacía 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 simplemente crean una clase o un espacio de nombres.
- El archivo sidecroller.jsl necesita que se carguen todos los demás archivos (es el bucle principal del juego).
Los archivos independientes todos fin 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 generará 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 primero línea. De esa manera, 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, se puede ejecutar cualquier archivo editado. 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 se configuran las inclusiones arriba, utilizará la copia de la pestaña de edición actual sin necesidad de guardar.
Lista de deseos:
- guarde todo antes de ejecutar (o haga incluir usar la copia de la pestaña de edición)
- especifique 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. Abajo, JSL crea el cielo azul, las montañas, los árboles y el camino de grava. La pared de roca es un mapa de bits en mosaico y los vampiros, murciélagos y osos son secuencias animadas de mapas de bits. Créditos al final.
The little bear is idling; the vampires are releasing bats or disappearing.The little bear is idling; the vampires are releasing bats or disappearing.The little bear is idling; the vampires are releasing bats or disappearing.The little bear is idling; the vampires are releasing bats or disappearing.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 existe una manera fácil de, por ejemplo, hacer un gráfico de líneas sobre un fondo transparente y tomar ese mapa de bits con el fondo transparente. (Existe 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 convertirlo nuevamente en un PNG sobre 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 sea necesario ampliarlo para sus primitivos favoritos. Observe los árboles con hojas no del todo opacas y espacios entre las hojas. Árbol Bézier usando Path() Describe cómo 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 recorrido; cada trozo de grava se dibuja en un mapa de bits.
El archivo ASSETS.jsl conoce los recursos de mapa de bits que utiliza el juego, cómo abrirlos, cómo normalizarlos y cómo nombrarlos. Debido a que las animaciones las dibujan 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 al vampiro liberando murciélagos está desplazado (.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á alejada y se puede ver que el cielo no es más grande, de izquierda a derecha, que la ventana. No se desplaza. Pero las capas cada vez más cercanas se vuelven cada vez 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.Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.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. Alejado, ve al vampiro a punto de entrar al escenario.
A medida que el osito camina hacia la derecha, las posiciones de los mapas de bits se ajustan...
Not afraid.Not afraid.Not afraid.Not afraid.Not afraid.No tiene miedo.
Pasando la mitad...
The NPCs animate even when off the display.The NPCs animate even when off the display.The NPCs animate even when off the display.The NPCs animate even when off the display.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.The little bear has reached the right-hand end of his world.The little bear has reached the right-hand end of his world.The little bear has reached the right-hand end of his world.The little bear has reached the right-hand end of his world.El osito ha llegado al final 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.Without all the layers, the characters seem to float.Without all the layers, the characters seem to float.Without all the layers, the characters seem to float.Without all the layers, the characters seem to float.Sin todas las capas, los personajes parecen flotar.
3. Audio del juego
El sonido es una parte importante de una experiencia inmersiva. Utilicé LMMS para crear los sonidos de 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 algún 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 deja 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 )
),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 clave se repite hasta que se vuelve obsoleta un segundo después.
While( go,
if( tickseconds() > lastkeyexpire,
lastkey="";
);
…Las llaves guían al osito. Están implementados en la máquina de estados finitos (FSM) del oso. Un ejemplo que cambia el oso de inactivo a caminando está en la función Cycle() de BEAR.jsl:
If( begin,
laststate=m_state;
if( keystroke=="LEFT" & ( pFaceLeft=="faceright" | (pFaceLeft=="faceleft" & pName=="idle") ),
m_state = m_stateWalkLeft;Comenzar es verdadero si el ciclo de animación actual está al principio, lo que constituye un buen lugar para cambiar de estado. Parece extraño dejar la mitad de una animación de salto para una animación inactiva. Ha pasado un tiempo desde que escribí esa lógica que dice que el oso sólo debería empezar a moverse hacia la izquierda si está mirando hacia la izquierda y está inactivo, pero si está mirando hacia la derecha... ¿simplemente empezar a ir hacia la izquierda? Creo que eso permite una reversión instantánea al caminar y tal vez evite un cambio al mismo estado que también podría solucionarse de otra manera.
5. Juego
Hacer un juego atractivo es la parte difícil para mí. Contar una historia, crear una meta, agregar los acertijos. Este juego es principalmente un entorno que espera a un narrador de historias. 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;
);Al presionar la flecha hacia abajo se levantan rocas; el osito puede albergar hasta seis. Al presionar la barra espaciadora se lanza la piedra en la dirección que mira. El oso toma nota de la piedra arrojada RocaLanzadaDerechaDesde o Roca arrojada Izquierda De que se verifica mediante código en el bucle principal que puede ver a los vampiros y al 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 matar dos vampiros de un tiro tal como lo escribí). Los vampiros probablemente deberían estar en una lista que pueda admitir más de dos. En realidad, todos los NPC (personajes que no son jugadores) deberían estar en una lista y deberían subclasificarse a partir 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 sólo oscurecería cómo funciona este sencillo 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 diferente calidad.