Proviamo un progetto per gestire alcuni JSL
Molto tempo fa ho suggerito di scrivere un side-scroller https://community.jmp.com/t5/Uncharted/Wild-Road-Map/bc-p/20996/highlight/true#M5 . Se non stavi giocando negli anni '80, https://en.wikipedia.org/wiki/Parallax_scrolling ha alcuni esempi. I side-scroller sono giochi 2D in cui un personaggio si sposta da sinistra a destra e lo sfondo può avere più livelli che scorrono a velocità diverse per dare un senso di profondità 3D. Altre variazioni potrebbero essere anche i ponticelli della piattaforma con una dimensione a scorrimento verticale.
Ci sono problemi da risolvere:
- sviluppo di giochi (JMP può semplificare la scrittura?)
- grafica del gioco (che aspetto ha?)
- audio del gioco (hai sentito?)
- input di gioco (come mi muovo?)
- gioco (succede qualcosa?)
1. Sviluppo del gioco
Sto usando un progetto JMP (e, di recente, sto passando a un progetto autonomo JMP 16.) Sto iniziando ad apprezzare il progetto autonomo per un progetto che ha da cinque a dieci file indipendenti. Ha schede per i file aperti e punti di ancoraggio per la finestra di registro. Il progetto autonomo memorizza tutto in un file (zip, sotto le copertine) e lo decomprime e lo ricompone automaticamente quando viene aperto e chiuso.
Far funzionare questo gioco è stato più semplice perché il file per la grafica e il file per l'audio e il file per il gioco sono file separati e relativamente piccoli. Ciò rende facile trovare cose, come il JSL che gestisce il gioco tra i personaggi del gioco. Il progetto mostra i file in schede, in questo modo:
A JMP project with several files.Un progetto JMP con diversi file.
Ci sono nove file aperti non appena questo progetto si apre; il file che contiene la definizione della classe per i vampiri nel gioco è attualmente in vista. Non sono completamente a mio agio con quando i file vengono salvati di nuovo nel progetto e quando il progetto è stato ancora salvato, quindi utilizzo le icone evidenziate in alto a sinistra prima di chiudere il progetto. Ho anche agganciato il registro in basso a sinistra. I progetti sembrano essere isolati dal resto di JMP e l'uno dall'altro, quindi hanno il proprio registro.
Il file MakeAddin non fa parte del gioco, ma fa parte dello sviluppo del gioco. Se quella scheda è aperta e io la ^R (esegui), rigenererà il componente aggiuntivo. Cosa succede se eseguo un'altra scheda? Che JSL funzioni, che potrebbe non essere l'inizio della logica del gioco. Ma l'ho fatto sempre, per errore, quindi l'ho risolto in questo modo:
- C'è un file principale, game.jsl, che include tutti gli altri file in un ordine ragionevole.
- La maggior parte dei file sono indipendenti e creano semplicemente una classe o uno spazio dei nomi.
- Il file sidescroller.jsl necessita di tutti gli altri file caricati (è il ciclo di gioco principale).
I file indipendenti all fine insieme a
try(include("game.jsl"),if(!contains(char(exception_msg),"includes itself through a closed path"),show(exception_msg)));che eseguirà il file game.jsl dopo il file corrente. Il file game.jsl tenterà di includere nuovamente il file corrente e genererà un errore di inclusione ricorsivo, che viene ignorato, o qualche altro errore, che viene segnalato. Il file sidescroller, che deve essere l'ultimo, ha l'istruzione try come primo linea. In questo modo il file game.jsl include tutto il resto prima che venga eseguito il resto del file a scorrimento laterale.
Il file game.jsl ha questo aspetto
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 )
)
)
);Ora, qualsiasi file modificato può essere eseguito. Tranne... se più di un file viene modificato, assicurati di salvare quelli che non vengono eseguiti in modo esplicito! Include ottiene la copia del disco, non la copia della scheda di modifica. Ma se modifichi solo un file, a causa del modo in cui sono impostate le inclusioni sopra, utilizzerà la copia della scheda di modifica corrente senza dover salvare.
Lista dei desideri:
- salva tutto prima di eseguire (o, includi usa la copia della scheda di modifica)
- specificare un file di avvio durante l'esecuzione di un progetto (non il file di caricamento del progetto)
- documenta come il salvataggio di una scheda e il salvataggio del progetto sono uguali/diversi
- un modo per eseguire il comando salva tutto del progetto da JSL
2. Grafica di gioco
Questo progetto utilizza sia la grafica generata da JSL che la grafica bitmap scaricata. Sotto, il cielo blu, le montagne, gli alberi e il sentiero di ghiaia sono creati da JSL. La parete rocciosa è una bitmap piastrellata e i vampiri, i pipistrelli e l'orso sono sequenze bitmap animate. Crediti alla fine.
The little bear is idling; the vampires are releasing bats or disappearing.L'orsetto è inattivo; i vampiri stanno liberando pipistrelli o stanno scomparendo.
Il file RGBA.jsl è una parte importante della grafica JSL. JMP può disegnare con trasparenza in un grafico e può utilizzare bitmap con trasparenza in un grafico, ma non esiste un modo semplice, ad esempio, per creare un grafico a linee su uno sfondo trasparente e acquisire quella bitmap con lo sfondo trasparente. (C'è un'opzione, Sfondo trasparente per le immagini PNG dei report, che salva tutto tranne il grafico con trasparenza. Puoi modificare un SVG salvato per rimuovere lo sfondo, ma hai bisogno di un altro programma per trasformarlo di nuovo in un PNG su uno sfondo trasparente.)
Lista dei desideri
Il file RGBA crea una classe per la creazione di bitmap con pixel trasparenti in cui non viene disegnato nulla. Potrebbe essere necessario estenderlo per le tue primitive preferite. Dai un'occhiata agli alberi con foglie non del tutto opache e spazi vuoti tra le foglie. Bezier Tree usando Path() descrive il disegno degli alberi, ma non con il canale alfa. Le montagne sono diversi livelli di un algoritmo di disegno delle colline con parametri diversi e il cielo è un rettangolo ombreggiato da blu a nero non trasparente. Il sentiero di ghiaia può essere piuttosto lento da generare, ma abbastanza veloce una volta in esecuzione; ogni pezzo di ghiaia viene disegnato in una bitmap.
Il file ASSETS.jsl conosce le risorse bitmap utilizzate dal gioco, come aprirle, come normalizzarle e come assegnarle un nome. Poiché le animazioni sono disegnate da persone diverse, richiedono la normalizzazione per centrarle allo stesso modo. Tali informazioni sono mantenute nella struttura in questo modo
Assets:vampire_images["hit"] = {{"attack/hit_1",.,.,0.80,0.5},che mostra che il vampiro che libera i pipistrelli è spostato (.80 contro .5 in altri) nella bitmap per gestire l'animazione extra larga. I vampiri e l'orso ottengono i loro livelli nello scorrimento laterale e la loro trasparenza lascia intravedere gli strati più distanti.
Poiché il gioco è in realtà in un grafico, puoi sbirciare al suo interno con lo strumento zoom per capire i livelli. Qui è ingrandito e puoi vedere che il cielo non è più grande, da sinistra a destra, della finestra. Non scorre. Ma gli strati sempre più vicini diventano sempre più larghi in modo che possano scorrere di più.
Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normalmente il cielo riempie il display, da sinistra a destra. Rimpicciolito, vedi il vampiro che sta per entrare sul palco.
Mentre l'orsetto cammina verso destra, le posizioni delle bitmap vengono regolate...
Not afraid.Non spaventato.
Appena passato la metà...
The NPCs animate even when off the display.Gli NPC si animano anche quando sono fuori dal display.
E in fondo...
The little bear has reached the right-hand end of his world.L'orsetto ha raggiunto l'estremità destra del suo mondo.
I vampiri camminano più lenti dell'orso. Recupereranno. Ed eccolo qui con alcune delle immagini di sfondo e in primo piano cancellate...
Without all the layers, the characters seem to float.Senza tutti i livelli, i personaggi sembrano fluttuare.
3. Audio di gioco
Il suono è una parte importante di un'esperienza coinvolgente. Ho usato LMMS per creare i suoni in questo gioco e un vero designer del suono potrebbe fare molto meglio. Ma è comunque abbastanza buono perché i suoni possono sovrapporsi senza interrompersi a vicenda. Su Windows. Probabilmente su Mac se qualcuno con un Mac trova un programma da riga di comando in grado di riprodurre un suono. Grazie @Jeff_Perkinson per avermi aiutato con del codice audio per Mac. Per Windows, il file AUDIO.jsl utilizza RunProgram per eseguire PowerShell che ha accesso al sistema audio di Windows. Su Mac utilizza un riproduttore di suoni da riga di comando, /usr/bin/afplay. È possibile che JMP si arresti in modo anomalo se si tenta di uscire da JMP prima che tutti i suoni di riproduzione finiscano; lascia che la barra di avanzamento finale finisca di riprodurre tutti i suoni avviati.
4. Input di gioco
Questo gioco utilizza la tastiera come dispositivo di input posizionando un MouseBox attorno al Graph Box che contiene il gioco.
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 )
),Il MouseBox inoltra le sequenze di tasti a una minuscola funzione che ricorda il tasto e l'ora
lastkey=empty();
lastkeyexpire=tickseconds();
spriteRunner = function({key},{},
lastkey=key;
lastkeyexpire=tickseconds()+1;
);Altrove l'azione della chiave viene ripetuta finché non diventa obsoleta un secondo dopo
While( go,
if( tickseconds() > lastkeyexpire,
lastkey="";
);
…Le chiavi guidano l'orsetto. Sono implementati nella macchina a stati finiti (FSM) dell'orso. Un esempio che cambia l'orso da inattivo a camminare è nella funzione BEAR.jsl cycle():
If( begin,
laststate=m_state;
if( keystroke=="LEFT" & ( pFaceLeft=="faceright" | (pFaceLeft=="faceleft" & pName=="idle") ),
m_state = m_stateWalkLeft;Begin è vero se il ciclo di animazione corrente è all'inizio, il che rende un buon posto per cambiare stato. Sembra strano lasciare la metà di un'animazione di salto a un'animazione inattiva. È passato un po' di tempo da quando ho scritto quella logica che dice che l'orso dovrebbe iniziare a muoversi a sinistra solo se è rivolto a sinistra e inattivo, ma se è rivolto a destra...inizia ad andare a sinistra? Penso che permetta un'inversione istantanea quando si cammina e forse impedisca un cambiamento nello stesso stato che potrebbe anche essere curato in un altro modo.
5. Gioco
Realizzare un gioco avvincente è la parte difficile per me. Raccontare una storia, creare un obiettivo, aggiungere i puzzle. Questo gioco è principalmente un ambiente, in attesa di un narratore. Ma ho aggiunto un po' di gioco.
,/*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;
);Premendo la freccia in basso si sollevano le rocce; l'orsetto ne può contenere fino a sei. Premendo la barra spaziatrice si lancia la roccia nella direzione opposta. L'orso prende nota del sasso lanciato RockThrownRightFrom o RockThrown Sono partiti Da che è controllato dal codice nel ciclo principale che può vedere i vampiri e l'orso.
// check for interaction between bear and vampire
if(!ismissing(bearPlayer:m_RockThrownRightFrom),
if(bearPlayer:m_RockThrownRightFrom<GETPOS></GETPOS>
Colpire un vampiro con un sasso lo fa tornare indietro di 50 pixel. (In realtà, puoi ottenere due vampiri con una fava nel modo in cui l'ho scritto.) I vampiri dovrebbero probabilmente essere in una lista che potrebbe supportarne più di due. In realtà tutti gli NPC (personaggi non giocanti) dovrebbero essere in una lista e dovrebbero essere sottoclassi da una classe base comune per effettuare le chiamate a cycle() in un ciclo invece di
vampire1:cycle(stagepos);
vampire2:cycle(stagepos);
bearPlayer:cycle(stagepos,lastkey);
ma questo oscurerebbe solo come funziona questo semplice esempio.
Dovrebbero esserci due allegati: un file di progetto e un file aggiuntivo. (Non posso allegare il progetto alla community in questo momento, ma se installi il componente aggiuntivo, c'è una copia del progetto nella directory del componente aggiuntivo.)
Crediti d'arte
Il sito di grafica del gioco aperto ha molti più contenuti di qualità variabile.