Comment j'ai créé un jeu pour le Bloc-notes



En lisant des solutions inhabituelles de développeurs indépendants, je suis tombé sur l'or. Voici un article sur le jeu dans un éditeur de texte. Art, animation, intrigue - tout est comme il se doit.

J'ai créé le jeu Et pourtant ça fait mal ( peut-être que l'auteur voulait dire que ça fait mal, mais je pourrais utiliser cette option exprès - environ ).


Le projet peut être téléchargé ici , et le code peut être consulté sur Github .

Tout a commencé en 2017 avec la question: "Est-il possible de faire un jeu dans le Bloc-notes?" Puis j'ai juste souri. Trois ans se sont écoulés. Après avoir réfléchi à la façon dont tout fonctionnera et m'être assuré qu'il est réel, j'ai décidé de créer ce jeu.

Habituellement, vous appuyez sur un bouton et quelque chose se passe dans le jeu. Appuyez sur A et Mario saute. Tout est lié à la réception d'informations et de commentaires. Le jeu reçoit une entrée et affiche la sienne.

Dans le jeu pour le Bloc-notes, il y aura des modifications à l'entrée que l'utilisateur apporte au fichier et à la sortie, les modifications que le jeu apporte au fichier. Pour ce faire, l'application suit l'heure de la dernière sauvegarde du fichier. S'il a changé, le jeu lit le contenu du fichier et y introduit de nouvelles données.

Il y a un problème: le Bloc-notes de Microsoft ne vérifie pas si le fichier a été modifié. Je devrais enregistrer le fichier, le fermer et l'ouvrir à nouveau. La création d'un tel jeu est possible, mais cela ne semble pas très amusant. J'ai dû chercher une alternative.

Je peux comprendre votre déception en raison du fait que le jeu n'a pas été fait dans le bloc-notes habituel. Mon titre peut y être exécuté - juste un petit processus confus. J'ai décidé de sacrifier la fraîcheur du projet pour rendre le jeu plus agréable.



Alternative


J'ai dû chercher un autre éditeur de texte. La seule exigence était la mise à jour automatique des fichiers. Bien qu'un peu plus tard, vous verrez que j'ai utilisé une autre fonctionnalité.

Tout d'abord, Notepad ++ et Sublime Text sont venus à l'esprit. Mais ils ne ressemblent pas du tout au Bloc-notes en apparence; le charme du projet aurait complètement disparu. De plus, ils demandent au joueur s'il souhaite mettre à jour le fichier. C'est bien mieux que de fermer et d'ouvrir un fichier, mais cela distrait quand même du gameplay. Je voulais que le fichier soit mis à jour automatiquement. Puis le Bloc-notes2 a attiré mon attention. Il était presque parfait.

L'éditeur peut être configuré pour ressembler à MS Notepad, et surtout - il vérifie les modifications apportées au fichier. Mais tout comme Notepad ++ et Sublime Text, Notepad2 demande au lecteur si le fichier doit être modifié. Heureusement, l'éditeur a du code open source, et j'ai pu le peaufiner à la perfection.



Notepad2 est écrit en C. Je connais un peu ce langage, même si je ne peux pas être appelé expert. Un programmeur Javascript expérimenté sera capable de lire et de comprendre l'essence générale du code, mais il n'était pas si facile de comprendre le code source de Notepad2 pour effectuer les changements nécessaires.

Pour commencer, j'ai décidé de rechercher du texte dans la boîte de dialogue: «Le fichier a été modifié par un programme externe. Recharger le fichier? ". Il s'agit de la valeur de la variable utilisée comme argument dans la fonction de boîte de dialogue. Et je l'ai trouvée.

if ((iFileWatchingMode == 2 && !bModified && iEncoding == iOriginalEncoding) ||
        MsgBox(MBYESNO,IDS_FILECHANGENOTIFY) == IDYES) {

Ce code vérifie si le contenu du fichier a changé. S'il a changé, une fenêtre s'ouvre et le programme vérifie si l'utilisateur a choisi «Oui». Je n'avais besoin que de remplacer une pièce

MsgBox(MBYESNO,IDS_FILECHANGENOTIFY) == IDYES

à TRUE, et le programme a commencé à mettre à jour automatiquement le fichier. J'ai donc créé un rendu basé sur ASCII. Reste à créer un moteur adapté.

Dessin


Le jeu est fait avec amour: LÖVE est un framework open source pour les jeux 2D écrit en Lua. J'ai utilisé cette plateforme pendant de nombreuses années et j'ai même mis en place un tutoriel . Pour ce projet, le module LÖVE du système de fichiers a été principalement utilisé car il fournit toutes les fonctionnalités nécessaires. Habituellement, LÖVE crée une image qui s'affiche ensuite à l'écran.

J'avais presque besoin de la même chose: la sortie de l'art ASCII dans un fichier texte. J'ai commencé avec une maison et un oiseau, et l'oiseau était censé voler à travers un fichier. J'ai repris l'art que j'ai trouvé sur ASCII Art , mais seules les œuvres originales sont utilisées dans le jeu (à l'exception des polices).



Et:



télécharger de l'art, c'est simplement lire un fichier.

house = love.filesystem.read("art_house.txt")
bird = love.filesystem.read("art_bird.txt")

La maison est utilisée comme arrière-plan, j'ai donc commencé par dessiner cette image sur «l'écran». L'écran dans ce cas est home.txt.

love.filesystem.write("home.txt", house)

Je voulais que l'oiseau fonctionne de cette façon:

local x, y = 20, 40
drawArt(bird, x, y)

x est le numéro de colonne, y est le numéro de ligne. Par conséquent, il a cassé l'écran et l'oiseau en listes de lignes.

-- Get the current canvas
screen = love.filesystem.read("home.txt")

-- Create a table. A table is like an array.
screen_lines = {}

-- The lua pattern (.-)\n says capture everything until the \n (newline).
-- We add an \n to the end of the file so that it captures the last line as well.
for line in (screen .. "\n"):gmatch("(.-)\n") do
    table.insert(screen_lines, line)
end

Avec l'oiseau a fait de même. Maintenant, le code décrivant l'oiseau devait chevaucher le code de la maison. Voici ce dont j'avais besoin:

  1. Trouvez la ligne dans laquelle l'oiseau doit être tracé.
  2. Imprimez la ligne entière sur x.
  3. Imprimez le reste de la ligne, en commençant par x + la longueur de l'art avec l'oiseau.
  4. Créez une nouvelle ligne avec la première partie, l'oiseau et la partie restante.
  5. Répétez la même chose pour toutes les autres lignes.

En code, cela ressemble à ceci:

function drawArt(art, x, y)
    art_lines = getLines(art)

    -- In Lua, you can get the length of a table and string with #
    for i=1, #screen_lines do
        if i == y then
            for j=1 ,#art_lines do
                -- With string:sub(start, end) we can get part of a string
                local first_part = screen_lines[i]:sub(1, x - 1)
                local second_part = screen_lines[i]
                                    :sub(x + #art_lines[j], #screen_lines[i])
                screen_lines[i] = first_part .. art_lines[i] .. second_part
            end
        end
    end
end

Ce qui s'est passé:



Vous avez probablement remarqué que l'oiseau est un rectangle - les espaces sont utilisés dans son art. Pour corriger la situation, j'ai calculé le nombre d'espaces au début de chaque ligne et ajouté ce nombre aux coordonnées afin que seul l'art soit dessiné.

-- (%s) gets all the whitespace characters.
local spaces = art_lines[j]:match("(%s*)")
-- Remove the spaces from the line.
art_lines[i] = art_lines[i]:sub(#spaces + 1, #art_lines[i])
local first_part = screen_lines[i]:sub(1, x + #spaces - 1)
local second_part = screen_lines[i]:sub(x + #spaces + #art_lines[j], #screen_lines[i])
screen_lines[i] = first_part .. art_lines[i] .. second_part

C'est devenu beaucoup mieux:



Animation


J'ai commencé à ajouter plus de puces, par exemple, une animation:



toutes les images sont situées dans un fichier et séparées par la balise {{F}}. La balise est déterminée lors de la lecture et vous permet de définir la séquence d'images. Grâce à cela, nous obtenons une animation classique. Créez une minuterie et dessinez des images conformément à celle-ci.

{{F}}
_   _ 
 'v'
{{F}}
      
--v--
{{F}}
      
_/v\_

J'ai également implémenté la sortie du texte imprimé et affiché séparément un écran, un inventaire et une fenêtre pour entrer dans la solution. Il y avait un problème. Comment le jeu sait-il qu'un fichier a été ouvert? C'est la deuxième fonctionnalité dont j'ai parlé plus tôt.

Dans le code source de Notepad2, j'ai prescrit que le fichier soit enregistré immédiatement après ouverture. Le jeu vérifie ensuite si la dernière sauvegarde a changé. Elle découvre donc que le fichier était ouvert et peut le modifier.

// At the end of the FileLoad function.
// If the file is not new, and the file has not been reloaded, save it.
if (!bNew && !bReload) {
    FileSave(TRUE,FALSE,FALSE,FALSE);
}

En conséquence, j'ai obtenu un cadre dans lequel vous pouvez travailler. Il est temps de créer un jeu.

Pendant neuf jours de développement (à en juger par la date de création des fichiers gif), je l'ai fait:



si vous avez exécuté le jeu, vous savez qu'il n'a pas de texte ou d'animation imprimable. Il y avait plusieurs raisons à cela:

  • , HDD/SSD . . , .
  • . , , . , .
  • — , . . . , , -, . . , , . , , , — .


J'étais furieux que l'utilisateur doive faire glisser le fichier dans la fenêtre Notepad2 pour démarrer le jeu. En double-cliquant sur le fichier, le Bloc-notes ou un autre programme par défaut pour lire .txt a été ouvert. Il était possible d'enregistrer une commande qui a changé l'application pour de tels fichiers sur Notepad2, mais personnellement je n'aimerais pas qu'un jeu fasse une telle feinte sur mon ordinateur.

Peut-être retourner les paramètres d'origine lorsque vous fermez le jeu? C'est possible, mais il y aura un problème si le jeu plante ou se ferme de façon inattendue.

Toutes les décisions semblaient insuffisamment étayées jusqu'à ce que je réalise qu'au lieu des fichiers .txt habituels avec un autre «x», on pouvait utiliser. Pour être précis, le caractère Unicode est U + 0445 (minuscule cyrillique Ha). Afin de ne pas être confus, j'ai nommé le fichier * .tXt. En conséquence, tous les fichiers du jeu étaient avec une résolution * .tXt et par défaut ouverts dans le Bloc-notes2.

assoc .tXt=tXt
ftype tXt=[directory]/.notepad/Notepad2.exe "%1"

Le programme par défaut ne peut être affecté qu'en tant qu'administrateur. Si vous ouvrez le jeu sous un autre compte, les fichiers txt seront utilisés. Si vous ouvrez le fichier dans le bloc-notes normal, le jeu vous informera que vous devez faire glisser le fichier dans la fenêtre ouverte du bloc-notes. Ou exécutez-le en tant qu'administrateur pour qu'il s'ouvre en double-cliquant.

Motivation


En fait, tout a été fait il y a trois ans. Qu'est-ce que j'ai fait le reste du temps? Un exemple classique de manque de motivation.

Initialement, l'intrigue était un peu plus longue que maintenant. Le dragon a tué vos parents, vous devez vous rendre chez le forgeron Ferdan, alors il a forgé une épée. On pensait que l'épée était composée de trois matériaux qui devaient être récupérés. Cela a considérablement augmenté le volume du jeu et a poussé la fin du développement. Le jeu n'était pas très divertissant et j'ai abandonné le projet deux mois plus tard.

Mais je l'ai gardé dans ma tête tout le temps. J'ai débogué tout un framework qui m'a permis de créer un jeu dans le Bloc-notes, et le projet n'a pas décollé. Il fallait le terminer. En 2019, je n'ai réalisé pratiquement aucun projet. La déception m'a poussé à me décider: achever l'inachevé en 2020.

Et la voici. J'ai raccourci l'intrigue, je me suis donné un mois pour tout (il s'est avéré une semaine de plus) et je me suis précipité dans la bataille. Toujours postulé pour A MAZE. La remise des prix, respectivement, était fixée au 2 février. Il y avait donc de la motivation.

Conclusion


Je suis content d'avoir terminé le jeu. C'est incroyable depuis combien de temps le projet vient de collecter la poussière numérique, mais au final, un mois a suffi. Le jeu n'aurait pas dû être rendu aussi volumineux que je le souhaitais au début - un tel projet non standard ne devrait montrer que les fonctionnalités qui peuvent y être implémentées.

Et après? Un jeu de peinture? Jeu dans la calculatrice? Il est peu probable que je les fasse. Mais j'aime penser aux jeux qui utilisent des plateformes non traditionnelles.

All Articles