Writing a text game in Python / Ren'Py

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 any gameplay? Yes, and in his spare time, not looking up from his main job? This is already more interesting and in fact - quite simple. Interested, I ask for cat.

image

About a year ago, my friend and I decided to make a small text game approximately in the spirit of Sunless Sea and 80 days: about navigation, trade, exploring strange settlements and talking with strange personalities. There should have been a religion, or rather several, the main character would not be seen as the savior, the hero of the country and the glorified sailor, but a moderately unfortunate entrepreneur / adventurer who does not care about anyone, and replace the fashionable choice between lesser and greater evil with a choice between good and good: no grimdark for the sake of grimdark. Quite quickly, the main factions and characters, large ports, the political situation and a bunch of cute little things like spearfishing for octopuses (depicted on the KDPV) and a brilliant idea to give almost all the characters Hungarian names were thought up which sound more exotic than the usual European ones and cause some implicit sympathy. In general, there were a lot of wooden houses.

At that time, we had one writer and one programmer (that is, me) in the team. The requirements in the previous paragraph are more related to the setting and the spirit of the game, so my friend had to fulfill them, and before me there were questions of game design and engine functionality. Firstly, the player will spend most of the time reading the text and choosing the actions of the main character. To do this, you need only decent typography and the ability to write scripts with menus, options, and variables. Soon the artist joined in, so I had to think about the illustrations as well. Secondly, the game is about research and trade, so you need somewhere in an accessible form for the player to store information about the collected rumors and purchased goods (as well as process it in every way). And finally, in the game about navigation, you need a map and the ability to move around it; just the command “to swim to the tartars and listen to the tales of sea horses” clearly does not correspond to the spirit of the project. So, the engine must also support at least simple mini-games, and not be limited only to the display of text and calculation of game variables.

Why Ren'Py


I must say right away that we didn’t even try to write the engine from scratch: cycling is fun in itself, but ineffective if the goal is to release the game before retiring. We also did not consider the Interactive Fiction parser: it has a very small audience in English, and in Russian our project, if it were parsed, could interest several hundred people at best. But I want to, if you don’t make money, at least go through the green light and gain some kind of reputation. Fortunately, most current English-language text game developers have gone from non-profit hobby projects to professional game dev 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 occurred to me wasStorynexus from Failbetter games , developers of Fallen London and Sunless Sea. Projects on it are edited through the browser, hosted by Failbetter and accessible through the browser to players. Opportunities for monetization have been removed since last year. The main drawback, however, is not this, but that most of the events in Fallen London are represented by cards falling out of the deck, and making a game on Storynexus that does not use this metaphor is not a trivial task. Anyway, tightly binding your project to a third-party server with closed code, which theoretically can even stop working at any time, is quite risky.

There are two more good proprietary engines for Choose Your Own Adventure, that is, games of about our type: ChoiceScript and Inklewriter. Both promise excellent typography, ease of development (browser-based editor for Inklewriter, scripting language for ChoiceScript) and the possibility of commercial publication. Unfortunately, both allow you to do only pure CYOA: there is no way to add to the game anything other than the actual text, menu and illustrations. The attentive reader will exclaim: “But how so? In 80 days, there was a rather complicated inventory and travel interface, right? And in Sorcery! I definitely saw the battle! ”Alas, these systems were developed by Inkle Studios for specific games and in the editor there is neither them, nor at least any opportunity to make yourself the same. For the same reason (and also because it is, um , peculiar ) we abandoned Twine .

The only option that suited us was Ren'Py. This is a free open source open source engine for visual novels (for example, it is based on it that “Endless Summer” and “Katawa shoujo” are made), which is quite easy to configure for our tasks. The games are cross-platform: building a distribution for Win / Mac / Linux is a matter of pressing a single button, and you don’t even have to have the target OS on hand. Android and iOS have also been announced, and Ren'Py releases for mobile axes exist, but we ourselves do not aim at the mobile market yet and cannot talk about development for it. In addition, Ren'Py has a very friendly and vibrant community in Russian and English .

The simplest script on 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 “Speak a replica“ This ... SEMPAY !!! ”on behalf of the character nyasha1”, the script itself is written in an imperative style. Secondly, a subset of this language is Screen Language, in which you can assemble screens in a declarative style from a limited set of Displayables (that is, widgets: buttons, images, text fields, etc.) and configure their functionality. If the built-in features are not enough, then using Python you can add your own. We will deal with this in the next article, but for now we’ll deal with the script.

The script in Ren'Py consists of a sequence of replicas, actions with screens, and player input. About screens and input a little lower, but for a start we will deal with the characters. In the visual novel, they are created like this (code from the official tutorial, with minor changes):

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?\""

Two characters were created: the protagonist and Sylvie, both writing in pale blue to the standard window at the bottom of the screen. Sylvie also has a portrait that appears on the screen before she begins to speak. It looks like this:

image

If we were creating a visual novel, we would continue in the same spirit, but we are not going to show portraits of characters, and even a couple of dozen illustrations for the whole game. In addition, most of the text is not a direct speech of the characters, so it would be illogical to attach it to one of them. Better create a virtual storyteller character:

define narrator = Character(None, kind = nvl, what_color="#000000", size = 12)

His name is narrator; this is a special name that gives it all the text that is clearly not attributed to other characters (strictly speaking, his name is None, and 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 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 group of styles.nvl * styles (screens.rpy and options.rpy files, respectively), in which we specify the font, the background of the text field, the color of the menu, and everything else.

image

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) 
   " — Я всегда говорил: твои песенки — дерьмо, Люсьен, и я не понимаю, где ты только находишь музыкантов, согласных это исполнять!"

We will analyze line by line: first, the start label is announced, from which the game begins. This name is reserved and the engine will always go to it after clicking the “New Game” button, wherever it is in the script. Everything that follows the label is logically “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 quite obvious, but the following line does an important thing: removes all text from the nvl_screen screen. This is not done automatically, therefore, if you do not place nvl clear at the end of each page, the text will quietly creep off the screen and be displayed there until the screen is finally cleared. It seems to be a trifle, but I spent a lot more time debugging missing nvl clear than I am ready to admit. We’ll temporarily remove the freshly washed screen, to let the player enjoy the background, show the background, turn on the endless pause (that is, wait for the click) and start the story. As soon as the text starts to be displayed on nvl_screen, the screen itself will return to its place.

The line with a pause, by the way, is already in python: to include a single line it is enough to start 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 you no longer need to import them explicitly.

Add branching and variables


At this point, the game is a reader that displays text, changing backgrounds if necessary. Saving, rewinding, the main menu and settings already work out of the box. However, if we wanted to write an illustrated story, 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: 
                #show screen nvl
                nvl clear 
                hide screen nvl 
                scene bg monet_palace_image 
                $ Ren'Py.pause(None) 
               " — Я всегда говорил: твои песенки — дерьмо, Люсьен, и я не понимаю, где ты только находишь музыкантов, согласных это исполнять!" 

Now, after turning on the game, the user (or, rather, the developer) will be able to enter debug mode if desired, or skip the ready-made entry piece and start testing the piece from the last commit immediately. The line show screen nvl 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 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 of ten pages. Each such episode is described inside a separate label with zero indentation (it no longer has to be inside the start label or even in the same file with it), and transitions from one episode to another are performed by jumps. So we not only fight with dozens of levels of indentation, but also ensure the modularity of the code: each episode can be tested separately and it’s pretty easy to check which variables it reads, into which it writes, and where it allows to go.

In-game menus and variables are arranged exactly the same. 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 (that is, the period from the start of the game until the first time out to sea ), the title of the episode lazlo (that is, a binge from Lazlo, on which you can hire young loafers as sailors) and the name of the label itself. With this approach, the names are rather cumbersome, but it’s better than when testing that three months ago someone already created the ship_listing variable, set True God knows where and now the roll from one random event affects the outcome of another random event on the other end seas.

Instead of a conclusion


At this point, we have already reproduced on Ren'Py the functionality of the Choicescript and inklewriter mentioned above. 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 one using pure python.

image

Also popular now: