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 jeux à défilement latéral sont des jeux 2D dans lesquels un personnage se déplace de gauche à droite et l'arrière-plan peut comporter plusieurs calques qui glissent à des rythmes différents pour donner une impression de profondeur 3D. D'autres variantes pourraient é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 ça ?)
- entrée de jeu (comment puis-je me déplacer ?)
- jeu (est-ce qu'il se passe quelque chose ?)
1. Développement de jeux
J'utilise un projet JMP (et tout récemment, je suis passé à un projet autonome JMP 16.) Je commence à aimer le projet autonome pour un projet comportant cinq à dix fichiers indépendants. Il comporte 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écompresse et se remballe automatiquement au fur et à mesure de son ouverture et de sa fermeture.
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.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 projet JMP avec plusieurs fichiers.
Il y a neuf fichiers ouverts dès l'ouverture de ce projet ; le fichier contenant la définition de classe des vampires dans le jeu est actuellement visible. Je ne suis pas tout à fait à l'aise avec le moment où les fichiers sont réenregistrés dans le projet et le moment où le projet est déjà 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 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 du développement du jeu. Si cet onglet est ouvert et que je ^R (l'exécute), il régénérera le complément. Que se passe-t-il si j'exécute 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 dossiers 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 actuel. Le fichier game.jsl tentera de réinclure le fichier actuel et générera 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 d'abord doubler. De cette façon, le fichier game.jsl inclut tout le reste avant l'exécution du reste du fichier Sidescroller.
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 )
)
)
);Désormais, quel que soit le fichier modifié, il peut être exécuté. Sauf... 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 actuelle de l'onglet d'édition sans avoir besoin de l'enregistrer.
Liste de souhaits :
- enregistrez tout avant d'exécuter (ou faites en sorte que l'inclusion utilise 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 chargé lors du 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 une image bitmap carrelée 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.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.Le petit ours tourne au ralenti ; les vampires lâchent 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 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 récupérer ce bitmap avec l'arrière-plan transparent. (Il existe une option, Fond 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 reconvertir en PNG sur un arrière-plan transparent.)
Liste de souhaits
Le fichier RGBA crée une classe permettant de créer des bitmaps avec des pixels transparents où rien n'est dessiné. Il faudra peut-être l'étendre à vos primitives préférées. Découvrez les arbres aux feuilles pas tout à fait opaques et aux 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 collines 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 parcouru ; 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. Étant donné 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. Ces informations sont conservées 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 calques dans le défilement latéral et leur transparence laisse apparaître des calques plus distants.
Parce que le jeu est en fait sous forme de graphique, vous pouvez y jeter un coup d'œil avec l'outil de zoom pour comprendre les couches. Ici, le zoom est effectué 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 calques de plus en plus proches deviennent de plus en plus larges afin de pouvoir défiler davantage.
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.Normalement, le ciel remplit l'écran, de gauche à droite. En effectuant un zoom arrière, vous voyez le vampire sur le point d'entrer sur scène.
Au fur et à mesure que le petit ours marche vers la droite, les positions des bitmaps sont ajustées...
Not afraid.Not afraid.Not afraid.Not afraid.Not afraid.Pas peur.
Juste après le milieu...
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.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.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.Le petit ours a atteint l’extrémité droite de son monde.
Les vampires marchent plus lentement que l'ours. Ils rattraperont leur retard. 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.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.Sans toutes les couches, les personnages semblent flotter.
3. Audio du jeu
Le son est un élément important d’une expérience immersive. J'ai utilisé LMMS pour créer les sons dans ce jeu, et un bon concepteur sonore pourrait faire bien mieux. Mais ça reste quand même plutôt bien car les sons peuvent se superposer sans se couper. Sous Windows. Probablement sur Mac si quelqu'un possédant un Mac trouve un programme en ligne de commande capable de 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 de son en ligne de commande, /usr/bin/afplay. JMP risque de planter 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 Bear. Un exemple qui fait passer l'ours du ralenti à 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 inactive. Cela fait un moment que j'ai écrit cette logique selon laquelle l'ours ne devrait commencer à se déplacer vers la gauche que s'il est tourné vers la gauche et au repos, mais s'il est tourné vers la droite... il suffit de commencer à aller à gauche ? Je pense que cela permet une inversion instantanée lors de la marche et empêche peut-être un changement vers le même état qui pourrait également être pris en charge d'une autre manière.
5. Jouer au jeu
Créer un jeu convaincant est la partie la plus difficile pour moi. Raconter une histoire, créer un objectif, ajouter des énigmes. Ce jeu est avant tout un environnement, attendant 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;
);Appuyer sur la flèche vers le bas ramasse des roches ; le petit ours peut en contenir jusqu'à six. En appuyant sur la barre d'espace, vous lancez le rocher dans la direction opposée. L'ours note la pierre lancée dans RocheLancéDroiteDe ou RocheLancée Gauche Depuis qui est vérifié par un code dans la boucle principale qui permet de 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 comme je l'ai écrit.) Les vampires devraient probablement figurer dans une liste pouvant en supporter plus de deux. En fait, tous les PNJ (personnages non-joueurs) devraient être dans une liste et devraient ê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 attacher le projet à la communauté pour le moment, mais si vous installez le complément, il existe une copie du projet dans le répertoire du complément.)
Crédits artistiques
Le site d’art de jeu ouvert propose beaucoup plus de contenu de qualité variable.