Essayons un projet pour gérer du JSL
Il y a longtemps, j'ai suggéré d'écrire un side-scroller https://community.jmp.com/t5/Uncharted/Wild-Road-Map/bc-p/20996/highlight/true#M5 . Si vous ne jouiez pas à des jeux dans les années 80, https://en.wikipedia.org/wiki/Parallax_scrolling a quelques exemples. Les side-scrollers sont des jeux 2D dans lesquels un personnage se déplace de gauche à droite et l'arrière-plan peut avoir plusieurs couches qui glissent à des vitesses différentes pour donner une impression de profondeur 3D. D'autres variantes peuvent également être des cavaliers de plate-forme avec une dimension de défilement vertical.
Il y a des problèmes à résoudre :
- développement de jeux (JMP peut-il faciliter l'écriture ?)
- graphismes du jeu (à quoi ça ressemble ?)
- audio du jeu (avez-vous entendu cela ?)
- entrée de jeu (comment puis-je me déplacer ?)
- jeu (il se passe quelque chose ?)
1. Développement de jeux
J'utilise un projet JMP (et tout récemment, je passe à un projet autonome JMP 16.) Je commence à aimer le projet autonome pour un projet comportant cinq à dix fichiers indépendants. Il a des onglets pour les fichiers ouverts et des points d'ancrage pour la fenêtre du journal. Le projet autonome stocke tout dans un seul fichier (zip, sous les couvertures) et se déballe et se réemballe automatiquement au fur et à mesure qu'il est ouvert et fermé.
Faire fonctionner ce jeu a été plus facile car le fichier pour les graphiques, le fichier pour l'audio et le fichier pour le jeu sont des fichiers séparés et relativement petits. Cela facilite la recherche de choses, comme le JSL qui gère le jeu entre les personnages du jeu. Le projet affiche les fichiers dans des onglets, comme ceci :
A JMP project with several files.Un projet JMP avec plusieurs fichiers.
Il y a neuf fichiers ouverts dès l'ouverture de ce projet ; le fichier qui contient la définition de classe pour les vampires dans le jeu est actuellement en vue. Je ne suis pas complètement à l'aise avec le moment où les fichiers sont enregistrés dans le projet et quand le projet est encore enregistré, j'utilise donc les icônes en surbrillance en haut à gauche avant de fermer le projet. J'ai également ancré le journal en bas à gauche. Les projets semblent être isolés du reste de JMP et les uns des autres, ils ont donc leur propre journal.
Le fichier MakeAddin ne fait pas partie du jeu, mais fait partie du développement du jeu. Si cet onglet est ouvert et que je le ^R (exécute), il régénérera le complément. Que se passe-t-il si je lance un autre onglet ? Ce JSL s'exécute, ce qui n'est peut-être pas le début de la logique du jeu. Mais je faisais ça tout le temps, par erreur, alors je l'ai corrigé comme ceci :
- Il existe un fichier principal, game.jsl, qui inclut tous les autres fichiers dans un ordre raisonnable.
- La plupart des fichiers sont indépendants et créent simplement une classe ou un espace de noms.
- Le fichier sidescroller.jsl a besoin de tous les autres fichiers chargés (c'est la boucle principale du jeu).
Les fichiers indépendants tous fin avec
try(include("game.jsl"),if(!contains(char(exception_msg),"includes itself through a closed path"),show(exception_msg)));qui exécutera le fichier game.jsl après le fichier courant. Le fichier game.jsl tentera de ré-inclure le fichier actuel et lancera une erreur d'inclusion récursive, qui est ignorée, ou une autre erreur, qui est signalée. Le fichier Sidescroller, qui doit être le dernier, a cette instruction try comme première ligne. De cette façon, le fichier game.jsl inclut tout le reste avant que le reste du fichier Sidescroller ne s'exécute.
Le fichier game.jsl ressemble à ceci
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 )
)
)
);Maintenant, quel que soit le fichier édité, il peut être exécuté. Sauf que... si plusieurs fichiers sont modifiés, assurez-vous de sauvegarder ceux qui ne sont pas explicitement exécutés ! L'inclusion obtient la copie du disque, pas la copie de l'onglet d'édition. Mais si vous ne modifiez qu'un seul fichier, en raison de la façon dont les inclusions sont configurées ci-dessus, il utilisera la copie de l'onglet d'édition en cours sans avoir besoin d'enregistrer.
Liste de souhaits :
- enregistrez tout avant d'exécuter (ou faites en sorte d'utiliser la copie de l'onglet d'édition)
- spécifier un fichier de démarrage lors de l'exécution d'un projet (pas le fichier de chargement du projet)
- documenter en quoi l'enregistrement d'un onglet et l'enregistrement du projet sont identiques/différents
- un moyen d'exécuter la commande save-all du projet à partir de JSL
2. Graphiques du jeu
Ce projet utilise à la fois des graphiques générés par JSL et des graphiques bitmap téléchargés. Ci-dessous, le ciel bleu, les montagnes, les arbres et le chemin de gravier sont créés par JSL. La paroi rocheuse est un bitmap en mosaïque et les vampires, les chauves-souris et l'ours sont des séquences bitmap animées. Crédits à la fin.
The little bear is idling; the vampires are releasing bats or disappearing.Le petit ours tourne au ralenti ; les vampires libèrent des chauves-souris ou disparaissent.
Le fichier RGBA.jsl est une partie importante des graphiques JSL. JMP peut dessiner avec transparence dans un graphique et peut utiliser des bitmaps avec transparence dans un graphique, mais il n'existe pas de moyen simple, par exemple, de créer un graphique linéaire sur un arrière-plan transparent et de saisir ce bitmap avec l'arrière-plan transparent. (Il existe une option, Arrière-plan transparent pour les images PNG du rapport, qui enregistre tout sauf le graphique avec transparence. Vous pouvez modifier un SVG enregistré pour supprimer l'arrière-plan, mais vous avez besoin d'un autre programme pour le transformer en PNG sur un arrière-plan transparent.)
Liste de souhaits
Le fichier RGBA crée une classe pour créer des bitmaps avec des pixels transparents où rien n'est dessiné. Il faudra peut-être l'étendre pour vos primitives préférées. Découvrez les arbres avec des feuilles pas tout à fait opaques et des espaces entre les feuilles. Arbre de Bézier utilisant Path() décrit le dessin des arbres, mais pas avec le canal alpha. Les montagnes sont plusieurs couches d'un algorithme de dessin de colline avec différents paramètres, et le ciel est un rectangle ombré bleu à noir non transparent. Le chemin de gravier peut être assez lent à générer, mais assez rapide une fois en cours d'exécution ; chaque morceau de gravier est dessiné dans un bitmap.
Le fichier ASSETS.jsl connaît les ressources bitmap utilisées par le jeu, comment les ouvrir, comment les normaliser et comment les nommer. Parce que les animations sont dessinées par des personnes différentes, elles nécessitent une normalisation pour les centrer de la même manière. Cette information est conservée dans la structure comme celle-ci
Assets:vampire_images["hit"] = {{"attack/hit_1",.,.,0.80,0.5},qui montre que le vampire libérant des chauves-souris est décalé (0,80 contre 0,5 dans d'autres) dans le bitmap pour gérer l'animation extra large. Les vampires et l'ours ont leurs propres couches dans le défilement latéral et leur transparence laisse apparaître des couches plus éloignées.
Parce que le jeu est en fait dans un graphique, vous pouvez y jeter un coup d'œil avec l'outil de zoom pour comprendre les couches. Ici, il y a un zoom arrière et vous pouvez voir que le ciel n'est pas plus grand, de gauche à droite, que la fenêtre. Il ne défile pas. Mais les couches de plus en plus proches deviennent de plus en plus larges pour pouvoir défiler davantage.
Normally the sky fills the display, left-to-right. Zoomed out, see the vampire about to enter the stage.Normalement, le ciel remplit l'affichage, de gauche à droite. Zoom arrière, voir le vampire sur le point d'entrer dans la scène.
Au fur et à mesure que le petit ours se dirige vers la droite, les positions des bitmaps sont ajustées...
Not afraid.Pas peur.
Juste après le milieu...
The NPCs animate even when off the display.Les PNJ s'animent même lorsqu'ils sont hors de l'écran.
Et au fond...
The little bear has reached the right-hand end of his world.Le petit ours a atteint le bout droit de son monde.
Les vampires marchent moins vite que l'ours. Ils se rattraperont. Et le voici avec une partie des images d'arrière-plan et de premier plan supprimées...
Without all the layers, the characters seem to float.Sans toutes les couches, les personnages semblent flotter.
3. Audio du jeu
Le son est une partie importante d'une expérience immersive. J'ai utilisé LMMS pour créer les sons de ce jeu, et un bon concepteur sonore pourrait faire bien mieux. Mais ça reste plutôt pas mal car les sons peuvent se superposer sans se couper. Sous Windows. Probablement sur Mac si quelqu'un avec un Mac trouve un programme en ligne de commande qui peut jouer un son. Merci @Jeff_Perkinson pour m'avoir aidé avec du code audio Mac. Pour Windows, le fichier AUDIO.jsl utilise RunProgram pour exécuter PowerShell qui a accès au système audio Windows. Sur Mac, il utilise un lecteur audio en ligne de commande, /usr/bin/afplay. Vous risquez de faire planter JMP si vous essayez de quitter JMP avant la fin de la lecture de tous les sons ; laissez simplement la barre de progression finale finir de jouer tous les sons commencés.
4. Entrée de jeu
Ce jeu utilise le clavier comme périphérique d'entrée en plaçant une MouseBox autour de la Graph Box qui contient le jeu.
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 )
),La MouseBox transmet les frappes au clavier à une petite fonction qui mémorise la touche et l'heure
lastkey=empty();
lastkeyexpire=tickseconds();
spriteRunner = function({key},{},
lastkey=key;
lastkeyexpire=tickseconds()+1;
);Ailleurs, l'action de la clé est répétée jusqu'à ce qu'elle devienne obsolète une seconde plus tard
While( go,
if( tickseconds() > lastkeyexpire,
lastkey="";
);
…Les clés dirigent le petit ours. Ils sont implémentés dans la machine à états finis (FSM) de l'ours. Un exemple qui change l'ours de l'inactivité à la marche est dans la fonction BEAR.jsl cycle() :
If( begin,
laststate=m_state;
if( keystroke=="LEFT" & ( pFaceLeft=="faceright" | (pFaceLeft=="faceleft" & pName=="idle") ),
m_state = m_stateWalkLeft;Begin est vrai si le cycle d'animation actuel est au début, ce qui constitue un bon endroit pour changer d'état. Il semble étrange de laisser le milieu d'une animation de saut à une animation de ralenti. Cela fait un moment que j'ai écrit cette logique qui dit que l'ours ne devrait commencer à se déplacer vers la gauche que s'il fait face à gauche et qu'il est inactif, mais s'il fait face à droite... commencez simplement à aller à gauche ? Je pense que cela permet une inversion instantanée lors de la marche et empêche peut-être un changement dans le même état qui pourrait également être pris en charge d'une autre manière.
5. Jeu
Faire un jeu captivant est la partie la plus difficile pour moi. Raconter une histoire, créer un objectif, ajouter des énigmes. Ce jeu est principalement un environnement, en attente d'un conteur. Mais j'ai ajouté un peu de gameplay.
,/*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;
);Appuyez sur la flèche vers le bas pour ramasser des rochers ; le petit ours peut en contenir jusqu'à six. Appuyez sur la barre d'espace pour lancer le rocher dans la direction opposée. L'ours note la pierre lancée dans RockJetéDroiteDepuis ou RockJeté La gauche De qui est vérifié par code dans la boucle principale qui peut voir les vampires et l'ours.
// check for interaction between bear and vampire
if(!ismissing(bearPlayer:m_RockThrownRightFrom),
if(bearPlayer:m_RockThrownRightFrom<GETPOS></GETPOS>
Frapper un vampire avec une pierre le fait reculer de 50 pixels. (En fait, vous pouvez faire d'une pierre deux coups avec la façon dont je l'ai écrit.) Les vampires devraient probablement figurer dans une liste qui pourrait en supporter plus de deux. En fait, tous les PNJ (personnages non-joueurs) doivent être dans une liste et doivent être sous-classés à partir d'une classe de base commune pour effectuer les appels à cycle() dans une boucle au lieu de
vampire1:cycle(stagepos);
vampire2:cycle(stagepos);
bearPlayer:cycle(stagepos,lastkey);
mais cela ne ferait qu'obscurcir le fonctionnement de cet exemple simple.
Il doit y avoir deux pièces jointes : un fichier de projet et un fichier de complément. (Je ne peux pas joindre le projet à la communauté pour le moment, mais si vous installez le complément, il y a une copie du projet dans le répertoire du complément.)
Crédits artistiques
Le site d'art de jeu ouvert a beaucoup plus de contenu de qualité variable.