Skip to content

This repository contains the source code to create a Visual Novel game based on minimal edits using Python if it needed. And just setting up a few json files.

License

Notifications You must be signed in to change notification settings

Pawsanie/Novelist

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

Pygame Visual Novel:

This repository contains the source code to create a Visual Novel game based on minimal edits using Python if it needed.
And just setting up a few json files.

I was inspired to develop this code by the inability to use RenPy to create a game in the form in which I want.
As well as not wanting to learn RenPy scripting language.

In addition, writing your own game, almost from scratch, is quite interesting.

⚠️The code probably needs some light refactoring.⚠️
⚠️Please note that some non-game features are not fully implemented.⚠️

Disclaimer:

⚠️Using some or all of the elements of this code, You assume responsibility for any consequences!

⚠️The licenses for the technologies on which the code depends are subject to change by their authors.



Required:

The application code is written in python and obviously depends on it.
Python version 3.6 [Python Software Foundation License / (with) Zero-Clause BSD license (after 3.8.6 version Python)]:

Required Packages:

PyGame [GNU LGPL version 2.1]:

Used to create windows, surfaces and draw them on top of each other.
Also for flipping the screen and drawing the window icon.

Installing the Required Packages:

pip install pygame

If you want you can change this library by first downloading it from the repository and installing your version using the command.

pip install ./path/to/modified/lib

How to run the application:

To run the application, you need to run the 'Visual_novel_game.py' script with your shell.
Example of shell command:

python -B Visual_novel_game.py

File location:
./📂Data
   └── 📄Visual_novel_game.py

Settings of scenes:

Scene order:

To adjust the scene order, you need to change the json file 'screenplay.json'.
At the same time, the first scene must be named scene_01!
And the 'past_scene' key of 'scene_01' must be 'START'.
In the last scene next_scene key must be 'FINISH'.

File location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📄screenplay.json

Example of one scene in screenplay.json file:

{
  "scene_01": {
    "background": "back_ground_01",
    "actors": {
      "Character_01": {
        "character_start_position": "right",
        "character_pose": "3",
        "character_plan": "background_plan"
      },
      "Character_02": {
        "character_start_position": "middle",
        "character_pose": "2",
        "character_plan": "first_plan"
      }
    },
    "special_effects": false,
    "gameplay_type": "reading",
    "past_scene": "START",
    "next_scene": "test_scene_02",
    "sounds": {
      "music_channel": false,
      "sound_channel": "blank",
      "voice_channel": false
     }
   }
}

In this case, the keys indicate which scene was before 'scene_01' and which should be after (scene_02).
Scenes 'START' or 'FINISH' do not exist.
But the game focuses on its flags.
Please note that a 'gameplay_type' key value must be reading/choice/false where the first two options are strings.
Please note that an actors characters keys must match certain values:
character_start_position - may have values right/middle/left.
character_pose - can be any key from the dictionary 'characters_sprites.json'.
More about this further in "Characters and their sprites" paragraph.
character_plan - may have values background_plan/first_plan.
The nested dictionary of the "sounds" key contains the keys and values of the sound effects and music
that will be played at the start of the scene and will be interrupted at the transition to the next one.
Please note that the keys "voice_channel", "sound_channel" and "music_channel" can contain either a string with a name, without a file extension, or false as values.

Dialogues:

Game dialogues have to be writen in lang_tag.json file... eng.json as example...
And this tag must be writen in dialogues_localizations_data.json file.
You need to name localization language tags for translation in the game settings.

Files location:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📁Dialogues
                                       ├── 📁Choice
                                       │       └── 📄eng.json (Can be your localization)
                                       ├── 📁Reading
                                       │       └── 📄eng.json (Can be your localization)
                                       └── 📄dialogues_localizations_data.json

Example of one scene in 'eng.json' file in 'Reading' folder':

{
   "scene_01": {
    "who": {
      "text": "Test Chan",
      "color": "#00ffff"
    },
    "what": {
      "text": "Hello World!",
      "color": "#ffffff"
    }
  }
}

Example of 'eng.json' file in 'Choice' folder':

{
  "scene_01": false,

  "test_scene_02": {
    "choice_01": "Got to Scene 01",
    "choice_02": "ERROR!",
    "choice_03": "Go to Scene 03"
  },

  "test_scene_03": false

}

Scenes with 'false' keys can be omitted.

Example of 'dialogues_localizations_data.json' file:

{
  "language_flags": [
    "eng",
    "ru"
  ]
}

Characters and their sprites:

Information about the characters is stored in a 'characters_sprites.json 'file.
It needs to list the names by which the game will look for characters.
Sprite file name. And the x|y coordinates for sprite animations.
File location:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📄characters_sprites.json Example of 'characters_sprites.json' file:

{
  "Character_01": {
      "sprite": "blank",
      "sprite_sheet": false,
      "poses": {
        "1": {
          "x": [25, 280],
          "y": [49, 618]
        },
        "2": {
          "x": [330, 594],
          "y": [49, 618]
        }}},  
  
    "Character_02": {
    "sprite": "blank_pink",
    "sprite_sheet": true,
    "poses": {
      "1": "animation_1",
      "2": "animation_2",
      "3": "animation_3"
    }
}

In this case, two options for implementing a character sprite are indicated:
Character_01 is the character without animations.
A static Sprite does not require filling out a separate file and the coordinates of each pose are set right here.

Please note that the name of the sprite is indicated without the file extension.
The coordinates are in pixels.
Please note that the name specified here is how the key is used in the 'screenplay.json' file!
And this name is in no way related to the one you can set in the dialogs!
As example 'eng.json' file from 'Dialogues' folder.

Character_02 is the character with an animated sprite.
It is distinguished by the presence of an animation sprite sheet, which must be filled out separately in the json file in directory Sprite_Sheet_data/Characters.
The file name must match the name specified in the sprite key.
The poses key values must match the animation names from the sprite sheet file.

File location:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📁Sprite_Sheet_data
                                        └── 📁Characters
                                                └── 📄*.json (Can be your sprite sheet json)
Example of such a 'sprite sheet .json' file:

{
  "animation_1": {
    "frames": {
      "1": {
        "y": [20, 940],
        "x": [21, 342]
      },
      "2": {
        "y": [20, 940],
        "x": [400, 721]
      }
    },
    "time_duration": 1.0
  },

  "animation_2": {
    "frames":{
      "1": {
        "y": [1000, 1919],
        "x": [21, 342]
      },
      "2": {
        "y": [1000, 1919],
        "x": [400, 721]
      }
    },
    "time_duration": 2.0
  }
}

Sprite images must be in png format and stored in a 'Characters' folder.
Folder location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Images
                    └── 📁Characters
                            └── 🖼️*.png (Can be your image file)

Backgrounds and its sprites:

Information about the backgrounds and its sprites must be entered into the 'backgrounds_sprites.json' file.
The names that will be given here are used to create scenes in 'screenplay.json' fie.

File location:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📄backgrounds_sprites.json
Example of 'backgrounds_sprites.json' file:

{
  "back_ground_01": "blank",

  "exit_menu": "blank",
  "settings_menu": "blank",
  "load_menu": "blank",
  "save_menu": "blank",
  "settings_status_menu": "blank",
  "start_menu": "blank"
}

As you can see from the example, names are also used for static menus.

Sprites must be in jpg format and stored in a 'Backgrounds' folder.
Folder location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Images
                    └── 📁Backgrounds
                            └── 🖼️*.jpg (Can be your image file)

However, you can change the sprite's format requirement by modifying it in the code.

User Interface:

Information about the standard user interface is contained in 'ui_menu-name_buttons.json' files and 'ui_localizations_data.json'.
Localization of the standard interface is stored in the appropriate files: 'eng.json' as example.

Below examples with json`s describes the code that needs to be changed if you want to supplement the standard menus with your own.

Files locations:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Json_data
                              └── 📁User_Interface
                                       ├── 📁UI_Buttons
                                       │       ├── 📄ui_*_buttons.json (Can be your button file)
                                       │       └── 📁Localization
                                       │                ├── 📄eng.json (Can be your localization)
                                       │                └── 📄ui_buttons_localizations_data.json
                                       └── 📁UI_Menu_texts
                                                ├── 📄ui_*_menu_text.json (Can be your menu text file)
                                                └── 📁Localization
                                                         ├── 📄eng.json (Can be your localization)
                                                         └── 📄ui_menu_texts_localizations_data.json

Json interface settings files:

Example of 'ui_localizations_data.json' file:

{
  "ui_buttons_files": [
    "ui_exit_menu_buttons",
    "ui_game_menu_buttons",
    "ui_gameplay_buttons"
  ],
  "localizations": [
    "eng",
    "ru"
  ]
}

'ui_buttons_files' key is used in the 'Interface_Controller.py' file code.
In 'get_ui_buttons_dict' method of 'InterfaceController' class.

Please note that you need to write file names in ui_buttons_files key values.
Note that localization tags work similarly to scene text localization.

Example of start menu in './UI_Buttons/*/eng.json' file:

{
  "start_menu_new_game": "New game",
  "start_menu_continue": "Continue",
  "start_menu_load": "Load",
  "start_menu_settings": "Settings",
  "start_menu_creators": "Creators",
  "start_menu_exit": "Exit"
}

The values of these keys are written as text, on buttons that have text in them.

Example of buttons in ui_*_buttons.json file:

{
  "start_menu_new_game": {
    "type": "start_menu",
    "index_number": 0,
    "sprite_name": "game_menu_buttons",
    "font": null,
    "color": "#000000"
  },
  "start_menu_continue": {
    "type": "start_menu",
    "index_number": 1,
    "sprite_name": "game_menu_buttons",
    "font": null,
    "color": "#000000"
  }
}

'index_number' key contains the horizontal or vertical menu order of button as value.
Numbers can be negative.
Finding the order of the buttons and how it will be described in the 'UI_Button.py' file 'Button' class.
In 'coordinates' method of 'Button' class also uses the 'type' key value.
If you want to add your own menus, please note that the key values are hardcoded.

Please note that the 'sprite_name' key contains the name of the sprite, as the value.
Sprites must be in png format and stored in a 'Buttons' folder.
Folder location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Images
                    └── 📁User_Interface
                            └── 📁Buttons

Example of 'ui_menu_text_localizations_data.json' file:

{
  "ui_menus_text_files": [
    "ui_back_to_start_menu_status_menu_text",
    "ui_exit_menu_text",
    "ui_settings_status_text"
  ],
  "localizations": [
    "eng",
    "ru"
  ]
}

Arranged by analogy with the 'ui_localizations_data.json'.
Only instead of the 'ui_buttons_files' key the 'ui_menus_text_files'.

Example of text in './UI_Menu_texts/*/eng.json' file:

{
  "back_to_start_menu_status_menu_text": "Would you like to return to the main menu?\nAll unsaved progress will be lost!",

  "exit_menu_text": "Would you like to exit the game?\nAll unsaved progress will be lost!",

  "settings_status_menu_text": "Would you like to change the game settings?"
}

Note that you can use the line break '\n' character for text.

Example of 'ui_exit_menu_text.json' file:

{
  "type": "exit_menu",
  "text": "exit_menu_text",
  "coordinates": {
    "x": 1,
    "y": 1
  },
  "font": null,
  "color": "#FFFFFF",
  "substrate": "blank_big"
}

text key contains as value link to text in localisation.

coordinates key contains as value dictionary with multipliers for the coordinates on which the text will be positioned, from the center.

color key contains as value color like string.

Please note that the 'substrate' key contains the name of the sprite, as the value.
Sprites must be in png format and stored in a 'Menu_Substrate' folder.
Folder location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Images
                    └── 📁User_Interface
                            └── 📁Menu_Substrate

Learn more about coding your own interface:

Files locations:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁User_Interface
                              ├── 📄Interface_Controller.py
                              ├── 📄UI_Menu_Text.py
                              ├── 📄UI_Button_Factory.py
                              ├── 📁UI_Buttons
                               |       ├── 📄UI_Base_Button.py
                               |       └── 📄UI_*_Button.py (Can be your button file)
                              └── 📁UI_Menus
                                       └── 📄UI_*_menu.py (Can be your menu file)

Buttons for new menu:
To create new buttons, in any case, you need to update the collections of the ButtonFactory class from 'UI_Button_Factory.py' file.
If you want to use standard button coordinates.
Simply update the lists under the "Interface collections" comment.
Example:

# Interface collections:
yes_no_menus: tuple = (
    'exit_menu',
    'settings_status_menu',
    'back_to_start_menu_status_menu'
)
long_buttons_menus: tuple = (
    'game_menu',
    'settings_menu',
    'start_menu',
    'creators_menu'
)

In this case, all settings will be applied automatically.

If your menu will hase a different way of calculating button positions, then you need to create a new button class and make it inherit from the BaseButton abstract class.
You will also need to update the "button_collections" dictionary.
Example:

# Buttons collection:
button_collections: dict = {
    'yes_no_menus': {
        'button_object': YesNoButton,
        'allowable_menus': yes_no_menus
    },
    'long_buttons_menus': {
        'button_object': LongButton,
        'allowable_menus': long_buttons_menus
    }
}

Where is your "button_object" key value is your new class.
And "allowable_menus" key value is tuple from under "Interface collections" comment.

New menu objects:
You will need to modify the menus_collection dictionary of 'InputCommandsReactions' in 'Reactions_to_input_commands.py' file.
Add a new item with menu settings to the dictionary.
Example:

menus_collection: dict = {
'exit_menu': {
    'object': ExitMenu(),
    'menu_file': 'ui_exit_menu_buttons',
    'text_file': 'ui_exit_menu_text'
},
'settings_menu': {
    'object': SettingsMenu(),
    'menu_file': 'ui_settings_menu_buttons',
    'text_file': None
}}

As a key for your menu collection element will act "type" key in your menus json file.
Please note that None key is reserved for reading gameplay UI.
In a nested dictionary, the 'object' key value is your menu object. If your menu does not have static text, set the value of the 'text_file' key to 'None'.

You will also need to add your buttons to 'ui_localizations_data.json', and localization.json 'eng.json' as example.
And create and fill a new 'ui_*_menu_buttons.json' file, for your menu.

Finally, you will need to program your menu to work in a new python file.
The 'UI_Start_menu.py' as example.

Informative text for new menu:
If you need to add static text, with or without a background, to your new menu then a new menu needs to be added to 'ui_menu_text_localizations_data.json'.
In addition, you will need to create a new ui_*_menu_text.json for the new menu and fill it with correct data.

You will also need to add a new menu to 'UI_Menu_Text.py' MenuText class 'scale' method`s list.
Or you can use the standard coordinates by adding a list for such text at the beginning of the MenuText class.
Example:

# Set menu lists:
yes_no_menu_text_list: list[str] = [
    'back_to_start_menu_status_menu',
    'exit_menu',
    'settings_status_menu',
]
back_menu_text_list: list[str] = [
    'creators_menu'
]

The name and icon of the game window:

In order to change the program name, you need to change the value of the variable 'app_name' in 'Visual_novel_game.py'.
File location:
./📂Data
   └── 📄Visual_novel_game.py

Example of app_name variable:

app_name: str = "Visual Novel"

In order to change the program window icons, please replace the icon files in the 'Icons' folder.
Icons images must be in png format and have the default size and titles.
Folder location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📁Images
                    └── 📁User_Interface
                            └── 📁Icons

GamePlay:

All gameplay code is stored in the folder 'GamePlay'.
If you need to program your gameplay element, add it to the constructor of class 'GamePlayAdministrator' from 'GamePlay_Administrator.py' file.
'gameplay_input' method of this class control of gameplay.

Files locations:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁GamePlay
                              ├── 📄GamePlay_Administrator.py
                              └── 📄GamePlay_*.py (Can be your gameplay file)

Default game settings:

The default settings are stored in a file 'user_settings'.
The game reads them at startup and saves them there, with the consent to change by the user, after setting.

File location:
./:open_file_folder:Data
   └── 📁Assets
            └── 📄user_settings

Sound System:

The SoundDirector class is responsible for working with sound.
Inside, it works with three audio channels responsible for character speech, music and sound effects.
All sounds and music files must be in MP3 format.
You can read more about installing audio tracks to game scenes in paragraph "Settings of scenes".

Your sound files should be located in their appropriate directories.
However, there is a condition if you have multiple voice covers.
In this case, you will have to change the value of the "single_voiceover_language" attribute in the Sound_Director class from True to False.
After this, you will need to add a sub folder for your voice acting to the Voice folder.
And place sound files in it.
When this option is enabled, the audio track select from folder stored in the "voice_acting_language" attribute of the SettingsKeeper class will be automatically selected.
This attribute can be changed in the settings menu, or in the settings file.
Naturally, this voice acting does not have to be voice localization. But it is important to consider changes in the storage principle when enabling the option.

Files locations:
./📂Data
   └── 📁Assets
            ├── 📁Sounds
             |       ├── 📁Effects
             |        |       └── 🎵*.mp3 (Can be your sound file)
             |       ├── 📁Music
             |        |       └── 🎵*.mp3 (Can be your music file)
             |       └── 📁Voice
             |                ├── 🎵*.mp3 (Can be your sound file)
             |                └── 📁eng (Optional!!!: Can be your localization folder)
             |                        └── 🎵*.mp3 (Optional!!!:Can be your sound file)
            └── 📁Scripts
                     ├── 📁Application_layer
                      |       └── 📄Sound_Director.py
                     └── 📁Json_data
                              └── 📄menu_sound_settings.json

To install sounds and music in the menus, you need to modify the "menu_sound_settings.json" file.
Example of menu sound settings.:

{
  "start_menu": {
    "music_channel": "blank",
    "sound_channel": false
  }
}

The values of the sound and music keys must also be either false or the name of the file located in the corresponding folder.
If you add a sound effect, it will only play once.

How the program works:

'Visual_novel_game.py' initializes game and call 'GameMaster' class.
During the initiation process, the script creates the 'SettingsKeeper' object that is responsible for the game settings.
The GameMaster class control game loop and generates lower-level entities that control the gameplay.

  • StageDirector - Controls the actions on the stage.
    Controls who and what will say as well as the appearance of the characters.
    Manages the scene background.
    Generates a 'Character', 'Background' and 'DialoguesWords' objects used to control staging.
  • SoundDirector - Play music, sound effects and speech for the scene if necessary.
  • SceneValidator - controls the order of the scenes.
    Stores inside itself information about the type of scene with which the StageDirector and the SoundDirector.
  • InterfaceController - controls all interface with which the player can interact.
    Generates 'Button' instances with 'ButtonFactory' and make menus from them.
  • InputCommandsReactions - catches user commands inside the game and passes them inside the loop to other entities.
    Generates 'GamePlayAdministrator' and all menus objects.
  • Render - renders the image after the calculations.

Simplified: the InputCommandsReactions processes user commands.
The SceneValidator checks for changes.
The StageDirector builds a scene.
Or the InterfaceController switches menu.

Files locations:
./📂Data
   ├── 📄Visual_novel_game.py
   └── 📁Assets
            └── 📁Scripts
                     ├── 📁Application_layer
                     │       ├── 📄Game_Master.py
                     │       ├── 📄Reactions_to_input_commands.py
                     │       ├── 📄Scene_Validator.py
                     │       ├── 📄Settings_Keeper.py
                     │       ├── 📄Sound_Director.py
                     │       └── 📄Stage_Director.py
                     ├── 📁Game_objects
                     │       ├── 📄Background.py
                     │       ├── 📄Character.py
                     │       └── 📄Dialogues.py
                     ├── 📁GamePlay
                     │       └── 📄GamePlay_Administrator.py
                     ├── 📁Render
                     │       └── 📄Render.py
                     └── 📁User_Interface
                              ├── 📄Interface_Controller.py
                              └── 📄UI_Button_Factory.py

Please note that the name of some classes does not correspond to the files where they are contained.
But according to the meaning of the names of the given files, it is still clear where they are.

Logging:

The program creates a log file and writes messages about critical problems to it.
File location:
./📂Data
   └── 📄logg_file.txt

Save and Load system:

Game saves are located in the 'Saves' folder.
The game save is a subfolder with a simple json file marked as 'save' format and a png image.
Please note that the subfolder and the save file must have the same name.
Files locations:
./📂Data
   └── 📁Saves
            └── 📁AutoSave
                     ├── 📄AutoSave.save
                     └── 🖼️screen_preview.png
Example of 'AutoSave.save' file:

{
    "scene": "test_scene_03",
    "date": "2023-08-01_19:05:15"
}

The SaveKeeper class from 'Save_Keeper.py' file is responsible for working with saves.
File location:
./📂Data
   └── 📁Assets
            └── 📁Scripts
                     └── 📁Application_layer
                              └─── 📄Save_Keeper.py

What needs to be completed:

Settings Menu:

The 'SettingsKeeper' class already exists.
It must be used for business logic that will work in the menu.
The 'SettingsMenu' class and its menu can be used as an external wrapper, or modified.
I planned to use it as a wrapper and make a separate menu for each type of setting.

Known Bugs:

  • Incorrect sRGB profile:
libpng warning: iCCP: known incorrect sRGB profile

Appears due to extra information in the sRGB profile when converting to PNG.
Because of this, after the program ends, a warning message appears in the terminal.
Actually this warning is not an error. I'm just too lazy to re-save the sprite blanks correctly...
Don't be like me and save your sprites correctly!


Thank you for your interest in my work.

About

This repository contains the source code to create a Visual Novel game based on minimal edits using Python if it needed. And just setting up a few json files.

Topics

Resources

License

Stars

Watchers

Forks

Languages