How to make a text game? Yes, whatever. How to make a cross-platform text game in Russian with illustrations, sound, working saves, no problems with the Cyrillic alphabet, and with some kind of gameplay? Yes, and in his spare time, not looking up from the main work? Now this is more interesting and actually quite easy. Interested please under the cat.

About a year ago, my friend and I decided to make a small text game approximately in the spirit of Sunless Sea for 80 days: about navigation, trade, research of strange settlements and communication with strange personalities. Religion was supposed to appear there, but rather several, the main character wanted to see not a savior, a hero of the country and a famous navigator, but a moderately unlucky entrepreneur / adventurer, to whom nobody cares, and to choose between good and small evil and good: no grin on the grind for the sake of grimdark. The main factions and characters, large ports, the political situation and a lot of nice little things like the octopus underwater hunting (shown in the KDPV) and the brilliant idea to give almost all the characters Hungarian names that sound more exotic than the usual European and cause some implicit sympathy came up pretty quickly. In general, there was a lot of wooden houses.
At that time, we had one writer and one programmer in the team (that is, me). The requirements in the previous paragraph relate rather to the setting and the spirit of the game, so my friend had to perform them, and I was faced with the questions of game design and functionality of the engine. First, the player will spend most of his time reading the text and choosing the actions of the main character. All you need is a tolerable typography and the ability to write a script with menus, options and variables. Soon the artist joined, so it was necessary to think more about the illustrations. Secondly, a game about research and trading, so you need somewhere in the form available to the player to store information about the collected rumors and purchased goods (as well as in every way to handle it). And finally, in the game about navigation, you need a map and the ability to navigate through it; just the command “to sail to Tartars and listen to sea horse tales” is clearly not in keeping with the spirit of the project. This means that the engine should also support at least simple mini-games, and not be limited to only showing the text and calculating game variables.
')
Why Ren'Py
I’ll say that we didn’t even try to write the engine from scratch: cycling is fascinating in itself, but ineffective if the goal is to release the game before retirement. Also, we did not consider the parser Interactive Fiction: it has a very small audience in English, and in Russian our project, if it were a parser, could be of interest to several hundred people at best. And if you do not want to make money, then at least go through the greenlight and gain some kind of reputation. Fortunately, most of the current English-language text game developers have moved from non-profit hobby projects to professional game development just a few years ago. Therefore, the main engines are either open source or, in any case, free. Let's see what we are offered.
The first option that came to my mind was
Storynexus from
Failbetter games , the developers of Fallen London and Sunless Sea. Projects on it are edited through a browser, Failbetter are hosted and available to players through the browser. Opportunities for monetization from last year removed. The main disadvantage, however, is not this, but the fact that in Fallen London most of the events are represented by cards that fall out of the deck, and making a game on Storynexus that does not use this metaphor is not a trivial task. And in general, tightly tying your project to a third-party server with a closed code, which theoretically can stop working at all at any time, is quite risky.
There are two more good proprietary engines for Choose Your Own Adventure, that is, games like ours:
ChoiceScript and
Inklewriter . Both promise excellent typography, ease of development (browser editor for Inklewriter, scripting language for ChoiceScript) and the possibility of commercial publishing. Unfortunately, both allow you to do only pure CYOA: there is no way to add something other than the actual text, menus and illustrations to the game. An attentive reader will exclaim: “But how is that? In
80 days there was a rather complicated inventory and travel interface, right? And in
Sorcery! I definitely saw a boevka! ”Alas, these systems were developed by Inkle Studios for specific games and in the editor there are neither them nor any possibility to make the same for myself. For the same reason (and also because he,
uh ,
kind of ) we abandoned
Twine .
The only option that satisfied us was Ren'Py. This is a free open-source engine for visual novels (for example, “Infinite Summer” and “Katawa shoujo” are made on it), which is quite easy to configure for our tasks. Games are cross-platform: building a distribution under Win / Mac / Linux is a matter of pressing a single button, and you don’t even need to have a target OS at hand. Android and iOS are also announced and Ren'Py-releases for mobile axes exist, but we ourselves are not aiming at the mobile market and cannot tell about the development for it. In addition, Ren'Py has a very friendly and lively community in
Russian and
English .
The simplest scenario at Ren'Py
Ren'Py is written in Python 2.7 + Pygame and has its own DSL. In this language, firstly, due to commands like “Show bg_city_night_53.png as a background without animation” or “Say a replica of“ CEM ... SEMPAY !!! ”in the name of the character nyasha1” in the imperative style, the script itself is written. Secondly, a subset of this language is Screen Language, where you can collect declarative styles from a limited set of Displayables (i.e., widgets: buttons, images, text fields, and the like) screens and customize their functionality. If the built-in features are not enough, you can add your own using Python. This we will do in the next article, but for now let's deal with the script.
The Ren'Py scenario consists of a sequence of replicas, actions with screens and player input. About the screens and input a little lower, but first we will deal with the characters. In the visual novel, they are created like this (code from the official tutorial, with minor edits):
define m = Character('Me', color="#c8c8ff") define s = Character('Sylvie', color="#c8ffc8") image sylvie smile = "sylvie_smile.png" label start m "Um... will you..." m "Will you be my artist for a visual novel?" show sylvie smile s "Sure, but what is a \"visual novel?\""
Created by two characters: the protagonist and Sylvie, both write in pale blue in the standard window at the bottom of the screen. Sylvie also has a portrait that appears on the screen before she starts speaking. It looks like this:

If we created a visual novel, we would continue in the same vein, but we are not going to show portraits of the characters, and a couple of dozen illustrations for the whole game. Most of the text in addition is not a direct speech of the characters, so it would be illogical to tie it to one of them. Better create a virtual character narrator:
define narrator = Character(None, kind = nvl, what_color="#000000", size = 12)
His name is narrator; this is a special name that gives him all the text, clearly not attributed to other characters (strictly speaking, his name is None, but narrator, like m and s in the previous example, is a variable in which the character's object is placed and from which its methods are called, for example , say) The kind argument takes two values: adv and nvl. The first is the default behavior described above, and the second includes the nvl mode, in which the portraits are not shown, and the text field occupies most of the screen. Just what we needed. This mode is described by the nvl_screen screen in the screens.rpy file and the styles.nvl * style group (screens.rpy and options.rpy files, respectively), in which we define the font, the background of the text field, the menu color and everything else.

label start: image bg monet_palace_image = Image('images/1129_monet_palace.jpg', align=(0 .5, 0.5)) nvl clear hide screen nvl scene bg monet_palace_image $ Ren'Py.pause(None) " — : — , , , , !"
Let's sort line by line: first, the start label is announced, from which the game will start. This name is reserved and the engine will always go to it after clicking the “New Game” button, no matter where it is in the script. Everything that follows the label is logically located “inside” this label, therefore it is distinguished by indentation: it works in Ren'Py in the same way as in pure python. The initialization of the picture is fairly obvious, but the next line does an important thing: removes all the text from the nvl_screen screen. This is not automatically done, so if you do not place nvl clear at the end of each page, the text will quietly crawl off the screen and will be displayed there until the screen is finally cleared. It seems to be a trifle, but I spent much more time debugging the missed nvl clear than I am ready to admit. With the freshly washed screen, we temporarily remove it to allow the player to admire the background, show the background, turn on an infinite pause (that is, wait for the click) and start the story. As soon as the text starts to appear on nvl_screen, the screen will return to its place.
The line with a pause, by the way, is already on python: to include a single line, it is enough to start it with '$', and longer pieces of code need to be written inside the 'python:' block. Any code executed by the game sees the modules of Ren'Py itself and obviously do not need to import them.
Adding Branches and Variables
At this point, the game is a reader that shows the text, changing the backgrounds if necessary. Saving, rewind, main menu and settings are already working out of the box. However, if we wanted to write an illustrated story, then we would have written it, right? Add a small menu before the text:
label start: menu: " ": $ debug_mode = True jump debug_menu " ": jump the_very_start_lazlo_nooptions " ": label the_very_start:
Now, after the game is turned on, the user (or rather, the developer) can, if he wishes, enter debug mode or skip the already completed entry piece and start testing a piece from the last commit at once. The show screen nvl line is commented out as unnecessary - as I mentioned above, the screen will appear by itself when the text is updated on it. Comments, as you can see, work in an absolutely obvious way.
Labels, menus, and other indented blocks can be nested to arbitrary depth, but in practice we try to split the text into episodes into ten pages. Each such episode is described inside a separate label with zero indentation (it is no longer necessary to be inside the start label or even in the same file with it), and transitions from one episode to another are performed by jumping. So we not only struggle with dozens of indentation levels, but also ensure the modularity of the code: each episode can be tested separately and it is quite easy to check which variables it reads, which variables it writes and where it goes.
In-game menus and variables are arranged in exactly the same way. Since there are an incredible amount of variables and labels even in a small episode for ten minutes of the game, we adopted a simple version of the Hungarian notation: the name of the label 'the_very_start_lazlo_nooptions' consists of three parts: the name of the location the_very_start (i.e. the period from the beginning of the game to the first exit to the sea ), the title of the episode of lazlo (that is, the drinking from Laszlo, where you can hire young idlers as sailors) and the name of the actual label. With this approach, the names are quite cumbersome, but it’s better than to find out when testing that three months ago someone created the ship_listing variable, set God True where and now the heel from one random event affects the outcome of another random event at the other end seas.
Instead of conclusion
At this point, we have already reproduced on Ren'Py the functionality of the above-mentioned Choicescript and inklewriter. It seems that our boat is ready to sail. In the next article, I will show how you can create a more complex interface using the on-screen language RenPy and even more complex on pure python.
