Lassen Sie uns ein Projekt ausprobieren, um etwas JSL zu verwalten
Vor langer Zeit schlug ich vor, einen Side-Scroller zu schreiben https://community.jmp.com/t5/Uncharted/Wild-Road-Map/bc-p/20996/highlight/true#M5 . Wenn Sie in den 80ern keine Spiele gespielt haben, https://en.wikipedia.org/wiki/Parallax_scrolling hat einige Beispiele. Side-Scroller sind 2D-Spiele, bei denen sich eine Figur von links nach rechts bewegt und der Hintergrund mehrere Ebenen haben kann, die mit unterschiedlichen Geschwindigkeiten verschoben werden, um ein Gefühl von 3D-Tiefe zu vermitteln. Andere Variationen könnten auch Plattform-Jumper mit einer vertikalen Scroll-Dimension sein.
Es gibt Probleme zu lösen:
- Spieleentwicklung (kann JMP das Schreiben erleichtern?)
- Spielgrafik (wie sieht es aus?)
- Spiel-Audio (hast du das gehört?)
- Spieleingabe (wie bewege ich mich?)
- Spielablauf (passiert etwas?)
1. Spielentwicklung
Ich verwende ein JMP-Projekt (und bin erst kürzlich zu einem eigenständigen JMP 16-Projekt gewechselt). Ich fange an, das eigenständige Projekt für ein Projekt mit fünf bis zehn unabhängigen Dateien zu mögen. Es hat Registerkarten für die geöffneten Dateien und Andockpunkte für das Protokollfenster. Das in sich geschlossene Projekt speichert alles in einer Datei (zip, unter der Decke) und entpackt und repackt automatisch, wenn es geöffnet und geschlossen wird.
Es war einfacher, dieses Spiel zum Laufen zu bringen, da die Datei für Grafiken und die Datei für Audio und die Datei für das Spiel separate, relativ kleine Dateien sind. Das macht es einfach, Dinge zu finden, wie die JSL, die das Gameplay zwischen Spielcharakteren verwaltet. Das Projekt zeigt die Dateien in Registerkarten wie folgt an:
A JMP project with several files.Ein JMP-Projekt mit mehreren Dateien.
Es sind neun Dateien geöffnet, sobald dieses Projekt geöffnet wird; Die Datei, die die Klassendefinition für Vampire im Spiel enthält, ist derzeit sichtbar. Ich bin nicht ganz zufrieden damit, wann die Dateien wieder in das Projekt gespeichert werden und wann das Projekt noch gespeichert ist, daher verwende ich die hervorgehobenen Symbole oben links, bevor ich das Projekt schließe. Ich habe auch das Protokoll unten links angedockt. Projekte scheinen vom Rest von JMP und voneinander isoliert zu sein, sodass sie ihr eigenes Protokoll haben.
Die MakeAddin-Datei ist nicht Teil des Spiels, aber Teil der Entwicklung des Spiels. Wenn diese Registerkarte geöffnet ist und ich sie mit ^R ausführe, wird das Add-In neu generiert. Was passiert, wenn ich einen anderen Tab ausführe? Dass JSL läuft, was vielleicht nicht der Beginn der Spiellogik ist. Aber ich habe das die ganze Zeit aus Versehen gemacht, also habe ich es so behoben:
- Es gibt eine Hauptdatei, game.jsl, die alle anderen Dateien in einer vernünftigen Reihenfolge enthält.
- Die meisten Dateien sind unabhängig und erstellen nur eine Klasse oder einen Namensraum.
- Die Datei "sidescroller.jsl" benötigt alle anderen geladenen Dateien (es ist die Hauptspielschleife).
Die unabhängigen Dateien alle Ende mit
try(include("game.jsl"),if(!contains(char(exception_msg),"includes itself through a closed path"),show(exception_msg)));Dadurch wird die Datei game.jsl nach der aktuellen Datei ausgeführt. Die Datei game.jsl versucht, die aktuelle Datei erneut einzuschließen, und gibt einen rekursiven Include-Fehler aus, der ignoriert wird, oder einen anderen Fehler, der gemeldet wird. Die Sidescroller-Datei, die an letzter Stelle stehen muss, hat diese try-Anweisung als Erste Linie. Auf diese Weise enthält die Datei game.jsl alles andere, bevor der Rest der Sidescroller-Datei ausgeführt wird.
Die Datei game.jsl sieht so aus
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 )
)
)
);Jetzt kann jede bearbeitete Datei ausgeführt werden. Außer ... wenn mehr als eine Datei bearbeitet wird, achten Sie darauf, diejenigen zu speichern, die nicht explizit ausgeführt werden! Das Include erhält die Disk-Kopie, nicht die Edit-Tab-Kopie. Wenn Sie jedoch nur eine Datei ändern, wird aufgrund der Art und Weise, wie die Includes oben eingerichtet sind, die aktuelle Kopie der Registerkarte "Bearbeiten" verwendet, ohne dass gespeichert werden muss.
Wunschzettel:
- Speichern Sie alles vor dem Ausführen (oder verwenden Sie zum Einschließen die Registerkarte „Bearbeiten“ kopieren)
- Geben Sie eine Startdatei an, wenn Sie ein Projekt ausführen (nicht die Datei zum Laden des Projekts).
- Dokumentieren Sie, wie das Speichern einer Registerkarte und das Speichern des Projekts gleich / unterschiedlich sind
- eine Möglichkeit, den Save-All-Befehl des Projekts von JSL aus auszuführen
2. Spielgrafik
Dieses Projekt verwendet sowohl JSL-generierte Grafiken als auch heruntergeladene Bitmap-Grafiken. Unten werden der blaue Himmel, die Berge, die Bäume und der Kiesweg von JSL erstellt. Die Felswand ist eine gekachelte Bitmap und die Vampire, Fledermäuse und Bären sind animierte Bitmap-Sequenzen. Credits am Ende.
The little bear is idling; the vampires are releasing bats or disappearing.Der kleine Bär ist faul; die Vampire lassen Fledermäuse frei oder verschwinden.
Die Datei RGBA.jsl ist ein wichtiger Bestandteil der JSL-Grafik. JMP kann mit Transparenz in einem Diagramm zeichnen und Bitmaps mit Transparenz in einem Diagramm verwenden, aber es gibt keine einfache Möglichkeit, beispielsweise ein Liniendiagramm auf einem transparenten Hintergrund zu erstellen und dieses Bitmap mit dem transparenten Hintergrund zu erfassen. (Es gibt eine Option Transparenter Hintergrund für PNG-Bilder des Berichts, die alles außer dem Diagramm mit Transparenz speichert. Sie können ein gespeichertes SVG bearbeiten, um den Hintergrund zu entfernen, aber Sie benötigen ein anderes Programm, um das wieder in ein PNG auf einem transparenten Hintergrund umzuwandeln.)
Wunschzettel
Die RGBA-Datei erstellt eine Klasse zum Erstellen von Bitmaps mit transparenten Pixeln, wo nichts gezeichnet wird. Es muss möglicherweise für Ihre bevorzugten Grundelemente erweitert werden. Schauen Sie sich die Bäume mit nicht ganz undurchsichtigen Blättern und Lücken zwischen den Blättern an. Bezier-Baum mit Path() beschreibt das Zeichnen der Bäume, aber nicht mit dem Alphakanal. Die Berge sind mehrere Schichten eines Hügelzeichnungsalgorithmus mit unterschiedlichen Parametern, und der Himmel ist ein nicht transparentes blau-schwarz schattiertes Rechteck. Der Schotterweg kann ziemlich langsam zu erzeugen sein, aber schnell genug, wenn er einmal läuft; Jeder Kies ist in einer Bitmap gezeichnet.
Die Datei ASSETS.jsl kennt die Bitmap-Assets, die das Spiel verwendet, wie man sie öffnet, wie man sie normalisiert und wie man sie benennt. Da die Animationen von verschiedenen Personen gezeichnet wurden, müssen sie normalisiert werden, um sie auf die gleiche Weise zu zentrieren. Diese Informationen werden so in der Struktur gehalten
Assets:vampire_images["hit"] = {{"attack/hit_1",.,.,0.80,0.5},die zeigt, wie der Vampir Fledermäuse loslässt, wird in der Bitmap verschoben (0,80 gegenüber 0,5 in anderen), um die extra breite Animation zu handhaben. Die Vampire und der Bär bekommen ihre eigenen Layer im Side-Scroller und ihre Transparenz lässt entferntere Layer durchscheinen.
Da sich das Spiel tatsächlich in einem Diagramm befindet, können Sie mit dem Zoom-Tool einen Blick darauf werfen, um die Ebenen zu verstehen. Hier ist es herausgezoomt und Sie können sehen, dass der Himmel von links nach rechts nicht größer ist als das Fenster. Es scrollt nicht. Aber die näher und näher liegenden Ebenen werden immer breiter, sodass sie mehr scrollen können.
Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normalerweise füllt der Himmel das Display von links nach rechts. Herausgezoomt, sehen Sie, wie der Vampir die Bühne betritt.
Während der kleine Bär nach rechts geht, werden die Positionen der Bitmaps angepasst...
Not afraid.Nicht ängstlich.
Kurz hinter der Mitte...
The NPCs animate even when off the display.Die NPCs werden auch außerhalb des Displays animiert.
Und ganz am Ende...
The little bear has reached the right-hand end of his world.Der kleine Bär ist am rechten Ende seiner Welt angelangt.
Die Vampire gehen langsamer als der Bär. Sie werden aufholen. Und hier ist es mit einigen gelöschten Hintergrund- und Vordergrundbildern ...
Without all the layers, the characters seem to float.Ohne all die Schichten scheinen die Charaktere zu schweben.
3. Spiel-Audio
Sound ist ein wichtiger Bestandteil eines immersiven Erlebnisses. Ich habe LMMS verwendet, um die Sounds in diesem Spiel zu erstellen, und ein richtiger Sounddesigner könnte das viel besser machen. Aber es ist immer noch ziemlich gut, weil sich die Sounds überlagern können, ohne sich gegenseitig abzuschneiden. Unter Windows. Wahrscheinlich auf dem Mac, wenn jemand mit einem Mac ein Befehlszeilenprogramm findet, das einen Ton abspielen kann. Vielen Dank @Jeff_Perkinson dafür, dass Sie mir mit etwas Mac-Audiocode geholfen haben. Für Windows verwendet die AUDIO.jsl-Datei RunProgram, um PowerShell auszuführen, das Zugriff auf das Windows-Soundsystem hat. Auf dem Mac verwendet es einen Befehlszeilen-Soundplayer, /usr/bin/afplay. Sie können JMP zum Absturz bringen, wenn Sie versuchen, JMP zu beenden, bevor alle abgespielten Sounds beendet sind; Lassen Sie einfach den letzten Fortschrittsbalken alle gestarteten Sounds abspielen.
4. Spieleingabe
Dieses Spiel verwendet die Tastatur als Eingabegerät, indem es eine MouseBox um die Graph Box herum platziert, die das Spiel enthält.
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 )
),Die MouseBox leitet Tastenanschläge an eine winzige Funktion weiter, die sich die Taste und die Uhrzeit merkt
lastkey=empty();
lastkeyexpire=tickseconds();
spriteRunner = function({key},{},
lastkey=key;
lastkeyexpire=tickseconds()+1;
);An anderer Stelle wird die Aktion der Taste wiederholt, bis sie eine Sekunde später abgestanden ist
While( go,
if( tickseconds() > lastkeyexpire,
lastkey="";
);
…Die Tasten steuern den kleinen Bären. Sie sind in der endlichen Zustandsmaschine (FSM) des Bären implementiert. Ein Beispiel, das den Bären vom Leerlauf zum Gehen ändert, ist die Funktion BEAR.jsl cycle():
If( begin,
laststate=m_state;
if( keystroke=="LEFT" & ( pFaceLeft=="faceright" | (pFaceLeft=="faceleft" & pName=="idle") ),
m_state = m_stateWalkLeft;Begin ist wahr, wenn der aktuelle Animationszyklus am Anfang steht, was eine gute Stelle zum Ändern von Zuständen darstellt. Es sieht seltsam aus, die Mitte einer springenden Animation einer Leerlaufanimation zu überlassen. Es ist schon eine Weile her, seit ich diese Logik geschrieben habe, die besagt, dass der Bär sich nur nach links bewegen sollte, wenn er nach links und im Leerlauf ist, aber wenn er nach rechts schaut ... einfach nach links gehen? Ich denke, das ermöglicht eine sofortige Umkehrung beim Gehen und verhindert möglicherweise einen Wechsel in den gleichen Zustand, der auch auf andere Weise erledigt werden könnte.
5. Spielablauf
Ein fesselndes Spiel zu machen, ist der schwierige Teil für mich. Eine Geschichte erzählen, ein Ziel schaffen, die Rätsel hinzufügen. Dieses Spiel ist hauptsächlich eine Umgebung, die auf einen Geschichtenerzähler wartet. Aber ich habe ein bisschen Gameplay hinzugefügt.
,/*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;
);Durch Drücken des Pfeils nach unten werden Steine aufgenommen; der kleine Bär kann bis zu sechs tragen. Durch Drücken der Leertaste wird der Stein in die Blickrichtung geworfen. Der Bär merkt sich den eingeworfenen Stein RockThrownRightFrom oder RockThrown Links Aus die durch Code in der Hauptschleife überprüft wird, die Vampire und den Bären sehen kann.
// check for interaction between bear and vampire
if(!ismissing(bearPlayer:m_RockThrownRightFrom),
if(bearPlayer:m_RockThrownRightFrom<GETPOS></GETPOS>
Wenn Sie einen Vampir mit einem Stein treffen, wird er 50 Pixel zurückgeworfen. (Eigentlich können Sie zwei Vampire mit einer Klappe bekommen, wie ich es geschrieben habe.) Die Vampire sollten wahrscheinlich in einer Liste sein, die mehr als zwei unterstützen könnte. Eigentlich sollten alle NPCs (Nicht-Spieler-Charaktere) in einer Liste stehen und von einer gemeinsamen Basisklasse abgeleitet werden, um die Aufrufe von cycle() stattdessen in einer Schleife durchzuführen
vampire1:cycle(stagepos);
vampire2:cycle(stagepos);
bearPlayer:cycle(stagepos,lastkey);
aber das würde nur verschleiern, wie dieses einfache Beispiel funktioniert.
Es sollten zwei Anhänge vorhanden sein: eine Projektdatei und eine Add-In-Datei. (Ich kann das Projekt derzeit nicht an die Community anhängen, aber wenn Sie das Add-In installieren, befindet sich eine Kopie des Projekts im Add-In-Verzeichnis.)
Kunstkredite
Die Open-Game-Art-Site hat viel mehr Inhalte von unterschiedlicher Qualität.