Short Description: This is a prologue to a visual novel with an interactive narrative. Created by myself using the engine Ren’Py and brush painting black ink and water on rice paper.
THE PROJECT
My project is a prologue to a visual novel, called The Grind, about spongey people that live in a world where it’s always nighttime. They have little light, most of which is provided by a giant manmade ‘star’ of fire in a globe near the center of the land where the more wealthy reside. The story follows a resilient ink grinder that provides ink to those who need it, for ink is the sustenance the sponge people require to survive. Black ink is banned because consuming it would turn the people dark, making them harder to see in the lack of light, causing disruptions. The ink grinder’s journey begins when the user makes a choice at the end of the prologue that determines which one of three narratives arches the novel will continue with. Here’s an image of the main menu.
INSPIRATION & THOUGHTS
I was inspired to make some type of graphic or visual novel back in sophomore year, when I read Yuumei’s Knite. I love both storytelling and creating art, so finding a combination really attracted me; it was more interactive than a regular comic book, interactivity being something I value in terms of connectivity, and the artwork supported the story and showed what the author was imagining – in this way I found the medium more intimate than, say, a movie or TV show. Because the author was laying out her words and drawing the related imagery straight from her imagination as opposed to finding actors that ‘best suit’ a character or a location that ‘best suits’ an imagined setting – never quite getting there. At the time, I felt that I wasn’t capable of producing good enough work and it would take me a lot of time when I had other classes to focus on. So I decided back then to leave the project for another time, and eventually came the time to do a capstone; the perfect time to make a graphic/visual novel of my own.
I chose to create a visual novel over a graphic novel; a distinction that’s lost to many people. A graphic novel is similar to (but not the same as ) a comic book, whereas a visual novel is a lot more interactive, incorporating decision making, branching narratives, and sometimes also other roles – for example playing an instrument. Knite is actually a flash comic. It took me some research to find what I really wanted to make – before, I’d just kind of lump the terms graphic/visual/kinetic together without really making a distinction, and even then I had trouble deciding whether I should include branching narratives to my visual novel. Soon, it became clear to me that branches were essential, especially as they contribute towards interactivity. Something that allows others to interact with it, experiment, find different paths, would lend itself to transportation: really being immersed in the story, the world, and even, if deep enough, affecting somebody’s views in the real world. Different paths, I believe, give readers the opportunity to explore and find out more about characters and viewpoints and thought processes, it gives you more insight into the storyworld, which hightens the impression of multiple dimensions, giving it more depth.
Of course, this was my first attempt at a project like this, and with a limited time to complete it, I couldn’t take it as deep as I might have liked and I still needed hands-on experience on what makes a visual novel successful; researching existing successes and their commonalities could only get one so far. Initially my plan was to create a novel, which of course would have taken a huge amount of time to make (especially if I was doing this individually), so it changed to a short story, which was still too time consuming, until I finally thought of creating a prologue. Still a lot of work. Usually, visual novels are team projects. I had to look up whether I doing something like this myself was even plausible. But a prologue would also allow me to have the opportunity to continue the project in the future, if I were to write/create the actual novel. So, my process, which stayed quite the same as I’d already planned back when I proposed my capstone, consisted of three parts: the story, the artwork and the code to put everything together.
THE STORY
I had trouble deciding what kind of story to write about, because I wished to create my artwork in ink with Chinese brush painting methods in a contemporary style – my currently preferred medium – and I wanted the story and art to mesh/flow together well. I also researched writing for visual novels because it’s not something I’d done before. Eventually, that indecisiveness lasted until I attended the first class of Professor David Perry’s Speculative Fictions course at the beginning of this semester (which I’d partially chosen to help me with capstone – goal achieved). He asked us to do a writing exercise, which forced me to just write something. And what I wrote turned out to be really suitable for my project; it incorporated the key component of my artwork into the story itself: the ink.
As I drafted my story, it became apparent to me that I’d have to change my plan from a short story to a prologue for what could be a larger visual novel due to time constraints. And because I was going to incorporate branching narratives in my story, I had to figure out what kind of branches I’d have. It seemed so complicated; there could be so many possibilities, yet they were hard to formulate. Once I bounced ideas off of a friend one night, which helped immensely, I figured out where the prologue would go.
Here is the structure of the story, showing the branches.
It seems slow-going, perhaps, but that’s because it is a prologue. There needs to be a satisfactory introduction to get readers accustomed to a very different (story)world, and having researched visual novels, I know that there cannot be branches all over the place or too soon; that would take you out of the story as well as put pressure on the author. If the readers had the ability to make narrative-altering decisions every few paces, the author would need to take them all into account and form new storylines that fit with the general narrative that runs throughout the VN – which puts into danger the integrity and cohesiveness of the whole thing. It begins turning into a different system more reliant on user input (called continuous interaction, allowing users to interact with the system at any point, as opposed to round-based interaction, where users are more limited and can only interact at certain points. I discuss this in my research paper. Citation: Endrass, Birgit, Christoph Klimmt, Gregor Mehlmann, Elisabeth André, Christian Roth. “Designing User-Character Dialog in Interactive Narratives: An Exploratory Experiment”. IEEE Transactions on Computational Intelligence and AI in Games, vol. 6, no. 2, 01 Jan. 2014, p. 166-173.)
Here’s the story draft.
The Grind: Prologue Draft
The ink grinder introduces the world to the reader a little
I live where it’s always nighttime. It’s not completely dark here; there’s always moonlight. I grind colored ink for those that want it – it’s nutritional and helps you keep your form. Unless it’s black ink you’re going for. Banned because it makes you hard to see in the darkness. Causes all sorts of disruptions. If you asked me whether I provide black ink, I’d tell you “no”. Fool.
Everybody wants to stay safe. But making ink isn’t too much of a cozy job. I have to leave, travel where there’s barely any light at all. None of that manmade everlasting fire “star” over in the central ring.
Well, we need to survive. So some of us have to get the job done. Being all spongey has its downsides though. Makes it a bit of a pain to hunt down colors. But I’m more absorbent than some, so I can last longer without ink out there. Makes for a good, if dangerous, living. The government gives me longer trips now, ‘cause who else will find the ingredients for black ink? But enough about that, I need to head out – into the town this time, not the darkness. Were you hoping I’d go?
[User answers, “Yes”: Well, you’ll just have to wait to see what it’s like out there.]
[User answers, “No”: Where’s your sense of curiosity? I gotta work on how I tell my stories if you’re not interested.]
The ink grinder goes out to the black market and meets somebody in the same business as him
My bag’s always half-packed. I take out the essentials, restock them if I need to before setting out again. Less effort spent that way – I am careful about it though. I guess I don’t want to die anytime soon. Let me take a draught of my ink mix before I leave the house. Hm… which one shall I take?
[User answers, “Purple”: Red and blue, huh… stamina and common sense are always good to have with you.]
[User answers, “Green”: Joy and common sense… No wonder yellow and blue can be tricky to grind up properly.]
[User answers, “Orange”: It’s a healthy combination, red and yellow. Gives you your strength and keeps you somewhat joyful.]
Alright, let’s go. I enjoy walking through the village, I really do. Fresh scent of ink any day. Maybe that’s why I do what I do.
We’ve got to go down here. The entrance isn’t well known to the public, obviously – would make it harder to run a black market, wouldn’t it? Let’s see… My table should be this way. I’ll set up.
“Where’d you get all that?” There’s somebody looking at my table. “I don’t manage to collect nearly as much…”
They strike up conversation, which goes one of three ways depending on which option(s) the reader chooses:
“One doesn’t give up their secrets so easily,” I say. It’s a bit wrong, I suppose. But we can’t offer information up whenever we’re asked; if we all know the good spots in the darkness, even if it is a big world, the ink will run out before we know it, and how else are we supposed to make a living? Hence the difference in rates…
He looks around, then leans in close and whispers, “Who’re you selling to?”
They get into an argument because the ink grinder provides black ink for the government, which the stranger is against
[User answers, “Why would I tell you?”]
He looks at me for a while. “You’d better not be saying you’re the governments pet or something.”
“I didn’t say anything.” I felt a bit irritated at this stranger asking questions.
He’s really looking at me up close. “The people who usually don’t say anything are the pets.”
I stared at him, realizing my mistake. I shouldn’t have gotten annoyed. Stress of the job, y’know. The thing is, the general custom, shall we say, here in the black market is that everybody is fairly open about who they’re selling to or why they’re buying. People are quite divided, picking sides.
The conversation remains neutral as they discuss the problem of the ill people, for which the ink grinder also provides black ink
[User answers, “For the sick.”]
He nods solemnly. “It’s awful how the government won’t provide the buy even for the ill, huh…”
“Do you sell to them, too?”
“Sometimes… My rates are a little higher, y’know, it’s hard out there.”
“Hmm… but the ill tend to be poor and they can’t afford too high a price.”
“I try to keep it reasonable, I can’t stand seeing them feel all miserable.”
“You seem to have a good heart in you.”
They get along well and the stranger invites him for drinks at a bar, where he will reveal something about the organization that wants to overthrow the government by luring creatures in from the darkness so that the poor may have more light
[User answers, “Oh, for whoever wants it, y’know…”]
He gives me a speculative look and asks, “You mean you’d be open to selling to anyone then?”
Curious. “Are you buying?”
“I might. Well?”
“It depends. I can’t just give to any old thief.”
“Well, I’m not any old thief, that’s for sure. Smashed my damn light one time, the punk.”
“Ah, no… they take forever to fix, don’t they?”
“That’s right. Had to handle the fire, wait for officials to show up, then for the repairmen. You ever been in that situation?”
“No… I have a little more luck.”
“Luck, eh… Is that how you collected so much?”
“I’m not revealing anything.”
“Well, I’m a little more interested in buying now. But seeing as everyone wants to know who’s buying from them… How about we meet at a bar later? I’ll tell you a little something I can’t go shouting around here.”
THE ARTWORK
To make the illustrations, I had decided to go with Chinese traditional brush painting methods in a contemporary art style. One of the things I found attractive about visual novels is that they are so flexible; you can really create whatever you want, for example educational materials. Usually, visual novels have artwork in the style of manga/anime, but I wanted to take advantage of the flexibility and do something really different, perhaps even encourage more people to do so. It would make for a larger audience, attracting those that don’t like the manga/anime style. So, my own style used ink and water on rice paper, provided to me by Professor Barbara Edelstein as I’d been taking her fine art classes. I used two different brushes, a comparatively thicker shuǐ mò brush and a thin gōng bǐ brush. I cut the rice paper into A5 size panels and would sometimes sketch lightly in pencil what I was going to draw; once you put something down in ink, it’s difficult to correct it. I added splashes of colored paint for the ink pot panel (second picture below).
My workstation in the art studio had these materials on it: my brushes, a brush holder, a container of water, an ink dish, ink bottle, rice paper, A5 size normal paper template to measure out the rice paper, ruler, scissors, eraser, pencil, laptop with my story pulled up, phone and earphones. It was a lot of stuff. As the creation of all the art panels for the story overlapped with putting the story and art together with code, I would pull up the project on my laptop and go through the panels, creating the illustrations for them one by one. Here are some pictures of my workstation on various days.
When it was time to start adding the illustrations to the project, I would edit the pictures I took of them using BeFunky, an online photo editor. It took some time to figure out how to resize the pictures, I experimented a lot, and eventually I went for cropping the edges, then resizing to 1280 x 914 pixels. This worked best when the pictures were added to Ren’Py. After I figured out how to edit the first image, the rest of them were done much faster.
THE CODE
Before starting the project, I’d looked up ways to put things together and came across the visual novel engine Ren’Py. I chose to use it over other options such as CloudNovel, Adobe Flash, or even PowerPoint, as was suggested in class, because it really provided the general format of a visual novel as well as a lot of flexibility for formatting and adding features the way I’d like to.
When a new project is created in the Ren’Py launcher, shown below, it provides you with four different code files. These are titled script, which is totally blank, options, gui, and screens.
I went through The Question game in the launcher, the Ren’Py quickstart to get familiarized with the engine, initially, then briefly through the tutorial in the launcher. Then I set to work writing my script code, after encouragement from Professor’s Clay Shirky and Scott Fitzgerald to start working on a draft version of my project so that I would get more familiar with using Ren’Py and prevent the chances of encountering problems later. Gradually, after writing out the story text with the appropriate ‘jumps’ when something branched and getting started on the artwork, I started adding to the draft version, declaring more background images as I created the art panels, as well as transitions.
I would go to Lemma Soft Forums whenever I would run into an issue I needed help with, a haven for people making visual novels.
In the end, I also added music – Puzzles by Tomohito Nishiura for Professor Layton and the Curious Village. I’d had the Professor Layton games in mind since the beginning because I felt like the calm, mysterious vibe was very fitting for my project. One of the main things that keeps the readers going is the mystery of what’s going to happen in the story.
This is the script code:
#Declare images used by this game. image bg intro = "IntroResized.jpg" image bg grindingink = "GrindingInkResized.jpg" image bg thievery = "ThieveryResized.jpg" image bg blackink = "BlackInkResized.jpg" image bg borderwall = "BorderWallResized.jpg" image bg themap = "TheMapResized.jpg" image bg forest = "ForestResized.jpg" image bg inkgrinder = "InkGrinderResized.jpg" image bg govink = "GovInkResized.jpg" image bg outsidetown = "OutsideTownResized.jpg" image bg yes = "YesResized.jpg" image bg no = "NoResized.jpg" image bg inkpots = "InkPotsResized.jpg" image bg setout = "SetOutResized.jpg" image bg entrance = "EntranceResized.jpg" image bg blackmarket = "BlackMarketResized.jpg" image bg igbm = "IGBMResized.jpg" image bg stranger = "StrangerResized.jpg" #Define characters used by this game. define g = Character('INK GRINDER', color="#990033") define s = Character('STRANGER', color="#7c4c44") #The game starts here. label start: scene bg intro with fade play music "ProfessorLaytonPuzzlesSuperExtended.mp3" g "I live where it’s always nighttime. It’s not completely dark here; there’s always moonlight." scene bg grindingink with fade "I grind colored ink for those that want it – it’s nutritional and helps you keep your form." scene bg thievery with fade "Unless it’s black ink you’re going for. Banned because it makes you hard to see in the darkness. Causes all sorts of disruptions." scene bg blackink with fade "If you asked me whether I provide black ink, I’d tell you \“no\”. Fool." scene bg borderwall with fade "Everybody wants to stay safe. But making ink isn’t too much of a cozy job. I have to leave, travel where there’s barely any light at all." scene bg themap with fade "None of that manmade everlasting fire \“star\” over in the central ring." scene bg inkgrinder with fade "Well, we need to survive. So some of us have to get the job done. Being all spongey has its downsides though." scene bg forest with fade "Makes it a bit of a pain to hunt down colors. But I’m more absorbent than some, so I can last longer without ink out there. Makes for a good, if dangerous, living." scene bg govink with fade "The government gives me longer trips now, ‘cause who else will find the ingredients for black ink?" scene bg outsidetown with fade "But enough about that, I need to head out – into the town this time, not the darkness. Were you hoping I’d go?" menu: "Yes": jump yes "No": jump no label yes: scene bg yes with fade "Well, you’ll just have to wait to see what it’s like out there." jump blackmarket label no: scene bg no with fade "Where’s your sense of curiosity? I gotta work on how I tell my stories if you’re not interested." jump blackmarket label blackmarket: "My bag’s always half-packed. I take out the essentials, restock them if I need to before setting out again. Less effort spent that way – I am careful about it though. I guess I don’t want to die anytime soon." scene bg inkpots with fade "Let me take a draught of my ink mix before I leave the house. Hm… which one shall I take?" menu: "Purple": jump purple "Green": jump green "Orange": jump orange label purple: scene bg grindingink with fade "Red and blue, huh… stamina and common sense are always good to have with you." jump village label green: scene bg grindingink with fade "Joy and common sense… No wonder yellow and blue can be tricky to grind up properly." jump village label orange: scene bg grindingink with fade "It’s a healthy combination, red and yellow. Gives you your strength and keeps you somewhat joyful." jump village label village: scene bg setout with fade "Alright, let’s go. I enjoy walking through the village, I really do. Fresh scent of ink any day. Maybe that’s why I do what I do." scene bg entrance with fade "We’ve got to go down here. The entrance isn’t well known to the public, obviously – would make it harder to run a black market, wouldn’t it?" scene bg blackmarket with fade "Let’s see… My table should be this way. I’ll set up." scene bg stranger with fade s "\"Where’d you get all that? I don’t manage to collect nearly as much…\"" scene bg igbm with fade g "\"One doesn’t give up their secrets so easily.\"" "It’s a bit wrong, I suppose. But we can’t offer information up whenever we’re asked; if we all know the good spots in the darkness, even if it is a big world, the ink will run out before we know it, and how else are we supposed to make a living? Hence the difference in rates…" scene bg stranger with fade s "\"Who’re you selling to?\"" menu: "\"Why would I tell you?\"": jump argument "\"For the sick.\"": jump neutral "\"Oh, for whoever wants it, y'know...\"": jump getalong label argument: s "\"You’d better not be saying you’re the government's pet or something.\"" scene bg igbm with fade g "\"I didn’t {i}say{/i} anything.\"" "I feel a bit irritated at this stranger asking questions. And he’s really looking at me up close." scene bg stranger with fade s "\"The people who usually don’t say anything are the pets.\"" "I stare at him, realizing my mistake. I shouldn’t have gotten annoyed. Stress of the job, y’know. The thing is, the general custom, shall we say, here in the black market is that everybody is fairly open about who they’re selling to or why they’re buying. People are quite divided, picking sides." return label neutral: s "\"It’s awful how the government won’t provide the buy even for the ill, huh...\"" scene bg igbm with fade g "\"Do you sell to them, too?\"" scene bg stranger with fade s "\"Sometimes… My rates are a little higher, y’know, it’s hard out there.\"" scene bg igbm with fade g "\"Hmm… but the ill tend to be poor and they can’t afford too high a price.\"" scene bg stranger with fade s "\"I try to keep it reasonable, I can’t stand seeing them feel all miserable.\"" scene bg igbm with fade g "\"You seem to have a good heart in you.\"" return label getalong: s "\"You mean you’d be open to selling to anyone then?\"" scene bg igbm with fade g "Curious. \"Are you buying?\"" scene bg stranger with fade s "\"I might. Well?\"" scene bg igbm with fade g "\"It depends. I can’t just give to any old thief.\"" scene bg stranger with fade s "\"Well, I’m not any old thief, that’s for sure. Smashed my damn light one time, the punk.\"" scene bg igbm with fade g "\"Ah, no… they take forever to fix, don’t they?\"" scene bg stranger with fade s "\"That’s right. Had to handle the fire, wait for officials to show up, then for the repairmen. You ever been in that situation?\"" scene bg igbm with fade g "\"No… I have a little more luck.\"" scene bg stranger with fade s "\"Luck, eh… Is that how you collected so much?\"" scene bg igbm with fade g "\"I’m not revealing anything.\"" scene bg stranger with fade s "\"Well, I’m a little more interested in buying now. But seeing as everyone wants to know who’s buying from them… How about we meet at a bar later? I’ll tell you a little something I can’t go shouting around here.\"" return
Besides the script file, the other three were already filled in with the basics – as well as advanced options that were commented out. I went through them and edited what I needed to. In the GUI file, I set the text sizes for dialogue and menus and choices, changed the font, changed the main menu and game menu background images, and edited the choice menus for in-game decisions. I removed some unnecessary elements of the GUI that clashed with the rest of it, such as default images that popped up for various menus. I set the width and height of the game to 1280 x 914 pixels to match the resized artwork. I went for purply colors to go with the constant nighttime theme of the story.
This is the GUI code:
################################################################################ ## Initialization ################################################################################ ## The init offset statement causes the init code in this file to run before ## init code in any other file. init offset = -2 ## Calling gui.init resets the styles to sensible default values, and sets the ## width and height of the game. init python: gui.init(1280, 914) ################################################################################ ## GUI Configuration Variables ################################################################################ ## Colors ###################################################################### ## ## The colors of text in the interface. ## An accent color used throughout the interface to label and highlight text. define gui.accent_color = '#942580' ## The color used for a text button when it is neither selected nor hovered. define gui.idle_color = '#942580' ## The small color is used for small text, which needs to be brighter/darker to ## achieve the same effect. define gui.idle_small_color = '#53478a' ## The color that is used for buttons and bars that are hovered. define gui.hover_color = '#5c5c5c' ## The color used for a text button when it is selected but not focused. A ## button is selected if it is the current screen or preference value. define gui.selected_color = '#5c5c5c' ## The color used for a text button when it cannot be selected. define gui.insensitive_color = '#5c5c5c' ## Colors used for the portions of bars that are not filled in. These are not ## used directly, but are used when re-generating bar image files. define gui.muted_color = '#a366a3' define gui.hover_muted_color = '#c199c1' ## The colors used for dialogue and menu choice text. define gui.text_color = '#53478a' define gui.interface_text_color = '#009999' ## Fonts and Font Sizes ######################################################## ## The font used for in-game text. define gui.text_font = "AQuietSleep.ttf" ## The font used for character names. define gui.name_text_font = "AQuietSleep.ttf" ## The font used for out-of-game text. define gui.interface_text_font = "AQuietSleep.ttf" ## The size of normal dialogue text. define gui.text_size = 26 ## The size of character names. define gui.name_text_size = 28 ## The size of text in the game's user interface. define gui.interface_text_size = 34 ## The size of labels in the game's user interface. define gui.label_text_size = 28 ## The size of text on the notify screen. define gui.notify_text_size = 16 ## The size of the game's title. define gui.title_text_size = 160 ## Main and Game Menus ######################################################### ## The images used for the main and game menus. define gui.main_menu_background = "gui/main_menu_test.jpg" define gui.game_menu_background = "gui/game_menu_test.jpg" ## Should we show the name and version of the game? define gui.show_name = True ## Dialogue #################################################################### ## ## These variables control how dialogue is displayed on the screen one line at a ## time. ## The height of the textbox containing dialogue. define gui.textbox_height = 185 ## The placement of the textbox vertically on the screen. 0.0 is the top, 0.5 is ## center, and 1.0 is the bottom. define gui.textbox_yalign = 1.0 ## The placement of the speaking character's name, relative to the textbox. ## These can be a whole number of pixels from the left or top, or 0.5 to center. define gui.name_xpos = 240 define gui.name_ypos = 0 ## The horizontal alignment of the character's name. This can be 0.0 for left- ## aligned, 0.5 for centered, and 1.0 for right-aligned. define gui.name_xalign = 0.0 ## The width, height, and borders of the box containing the character's name, or ## None to automatically size it. define gui.namebox_width = None define gui.namebox_height = None ## The borders of the box containing the character's name, in left, top, right, ## bottom order. define gui.namebox_borders = Borders(5, 5, 5, 5) ## If True, the background of the namebox will be tiled, if False, the ## background if the namebox will be scaled. define gui.namebox_tile = False ## The placement of dialogue relative to the textbox. These can be a whole ## number of pixels relative to the left or top side of the textbox, or 0.5 to ## center. define gui.dialogue_xpos = 268 define gui.dialogue_ypos = 50 ## The maximum width of dialogue text, in pixels. define gui.dialogue_width = 744 ## The horizontal alignment of the dialogue text. This can be 0.0 for left- ## aligned, 0.5 for centered, and 1.0 for right-aligned. define gui.dialogue_text_xalign = 0.0 ## Buttons ##################################################################### ## ## These variables, along with the image files in gui/button, control aspects of ## how buttons are displayed. ## The width and height of a button, in pixels. If None, Ren'Py computes a size. define gui.button_width = None define gui.button_height = 36 ## The borders on each side of the button, in left, top, right, bottom order. define gui.button_borders = Borders(4, 4, 4, 4) ## If True, the background image will be tiled. If False, the background image ## will be linearly scaled. define gui.button_tile = False ## The font used by the button. define gui.button_text_font = gui.interface_text_font ## The size of the text used by the button. define gui.button_text_size = gui.interface_text_size ## The color of button text in various states. define gui.button_text_idle_color = gui.idle_color define gui.button_text_hover_color = gui.hover_color define gui.button_text_selected_color = gui.selected_color define gui.button_text_insensitive_color = gui.insensitive_color ## The horizontal alignment of the button text. (0.0 is left, 0.5 is center, 1.0 ## is right). define gui.button_text_xalign = 0.0 ## These variables override settings for different kinds of buttons. Please see ## the gui documentation for the kinds of buttons available, and what each is ## used for. ## ## These customizations are used by the default interface: define gui.radio_button_borders = Borders(25, 4, 4, 4) define gui.check_button_borders = Borders(25, 4, 4, 4) define gui.confirm_button_text_xalign = 0.5 define gui.page_button_borders = Borders(10, 4, 10, 4) define gui.quick_button_borders = Borders(10, 4, 10, 0) define gui.quick_button_text_size = 14 define gui.quick_button_text_idle_color = gui.idle_small_color define gui.quick_button_text_selected_color = gui.accent_color ## You can also add your own customizations, by adding properly-named variables. ## For example, you can uncomment the following line to set the width of a ## navigation button. # define gui.navigation_button_width = 250 ## Choice Buttons ############################################################## ## ## Choice buttons are used in the in-game menus. define gui.choice_button_width = 790 define gui.choice_button_height = None define gui.choice_button_tile = False define gui.choice_button_borders = Borders(100, 5, 100, 5) define gui.choice_button_text_font = gui.text_font define gui.choice_button_text_size = gui.text_size define gui.choice_button_text_xalign = 0.5 define gui.choice_button_text_idle_color = "#53478a" define gui.choice_button_text_hover_color = "#ffffff" ## File Slot Buttons ########################################################### ## ## A file slot button is a special kind of button. It contains a thumbnail ## image, and text describing the contents of the save slot. A save slot uses ## image files in gui/button, like the other kinds of buttons. ## The save slot button. define gui.slot_button_width = 276 define gui.slot_button_height = 206 define gui.slot_button_borders = Borders(10, 10, 10, 10) define gui.slot_button_text_size = 14 define gui.slot_button_text_xalign = 0.5 define gui.slot_button_text_idle_color = gui.idle_small_color ## The width and height of thumbnails used by the save slots. define config.thumbnail_width = 256 define config.thumbnail_height = 144 ## The number of columns and rows in the grid of save slots. define gui.file_slot_cols = 3 define gui.file_slot_rows = 2 ## Positioning and Spacing ##################################################### ## ## These variables control the positioning and spacing of various user interface ## elements. ## The position of the left side of the navigation buttons, relative to the left ## side of the screen. define gui.navigation_xpos = 40 ## The vertical position of the skip indicator. define gui.skip_ypos = 10 ## The vertical position of the notify screen. define gui.notify_ypos = 45 ## The spacing between menu choices. define gui.choice_spacing = 22 ## Buttons in the navigation section of the main and game menus. define gui.navigation_spacing = 4 ## Controls the amount of spacing between preferences. define gui.pref_spacing = 10 ## Controls the amount of spacing between preference buttons. define gui.pref_button_spacing = 0 ## The spacing between file page buttons. define gui.page_spacing = 0 ## The spacing between file slots. define gui.slot_spacing = 10 ## The position of the main menu text. define gui.main_menu_text_xalign = 1.0 ## Frames ###################################################################### ## ## These variables control the look of frames that can contain user interface ## components when an overlay or window is not present. ## Generic frames that are introduced by player code. define gui.frame_borders = Borders(4, 4, 4, 4) ## The frame that is used as part of the confirm screen. define gui.confirm_frame_borders = Borders(40, 40, 40, 40) ## The frame that is used as part of the skip screen. define gui.skip_frame_borders = Borders(16, 5, 50, 5) ## The frame that is used as part of the notify screen. define gui.notify_frame_borders = Borders(16, 5, 40, 5) ## Should frame backgrounds be tiled? define gui.frame_tile = False ## Bars, Scrollbars, and Sliders ############################################### ## ## These control the look and size of bars, scrollbars, and sliders. ## ## The default GUI only uses sliders and vertical scrollbars. All of the other ## bars are only used in creator-written code. ## The height of horizontal bars, scrollbars, and sliders. The width of vertical ## bars, scrollbars, and sliders. define gui.bar_size = 36 define gui.scrollbar_size = 12 define gui.slider_size = 30 ## True if bar images should be tiled. False if they should be linearly scaled. define gui.bar_tile = False define gui.scrollbar_tile = False define gui.slider_tile = False ## Horizontal borders. define gui.bar_borders = Borders(4, 4, 4, 4) define gui.scrollbar_borders = Borders(4, 4, 4, 4) define gui.slider_borders = Borders(4, 4, 4, 4) ## Vertical borders. define gui.vbar_borders = Borders(4, 4, 4, 4) define gui.vscrollbar_borders = Borders(4, 4, 4, 4) define gui.vslider_borders = Borders(4, 4, 4, 4) ## What to do with unscrollable scrollbars in the gui. "hide" hides them, while ## None shows them. define gui.unscrollable = "hide" ## History ##################################################################### ## ## The history screen displays dialogue that the player has already dismissed. ## The number of blocks of dialogue history Ren'Py will keep. define config.history_length = 250 ## The height of a history screen entry, or None to make the height variable at ## the cost of performance. define gui.history_height = 140 ## The position, width, and alignment of the label giving the name of the ## speaking character. define gui.history_name_xpos = 150 define gui.history_name_ypos = 0 define gui.history_name_width = 150 define gui.history_name_xalign = 1.0 ## The position, width, and alignment of the dialogue text. define gui.history_text_xpos = 170 define gui.history_text_ypos = 5 define gui.history_text_width = 740 define gui.history_text_xalign = 0.0 ## NVL-Mode #################################################################### ## ## The NVL-mode screen displays the dialogue spoken by NVL-mode characters. ## The borders of the background of the NVL-mode background window. define gui.nvl_borders = Borders(0, 10, 0, 20) ## The height of an NVL-mode entry. Set this to None to have the entries ## dynamically adjust height. define gui.nvl_height = 115 ## The spacing between NVL-mode entries when gui.nvl_height is None, and between ## NVL-mode entries and an NVL-mode menu. define gui.nvl_spacing = 10 ## The position, width, and alignment of the label giving the name of the ## speaking character. define gui.nvl_name_xpos = 430 define gui.nvl_name_ypos = 0 define gui.nvl_name_width = 150 define gui.nvl_name_xalign = 1.0 ## The position, width, and alignment of the dialogue text. define gui.nvl_text_xpos = 450 define gui.nvl_text_ypos = 8 define gui.nvl_text_width = 590 define gui.nvl_text_xalign = 0.0 ## The position, width, and alignment of nvl_thought text (the text said by the ## nvl_narrator character.) define gui.nvl_thought_xpos = 240 define gui.nvl_thought_ypos = 0 define gui.nvl_thought_width = 780 define gui.nvl_thought_xalign = 0.0 ## The position of nvl menu_buttons. define gui.nvl_button_xpos = 450 define gui.nvl_button_xalign = 0.0 ## Localization ################################################################ ## This controls where a line break is permitted. The default is suitable ## for most languages. A list of available values can be found at https:// ## www.renpy.org/doc/html/style_properties.html#style-property-language define gui.language = "unicode" ################################################################################ ## Mobile devices ################################################################################ init python: ## This increases the size of the quick buttons to make them easier to touch ## on tablets and phones. if renpy.variant("touch"): gui.quick_button_borders = Borders(40, 14, 40, 0) ## This changes the size and spacing of various GUI elements to ensure they ## are easily visible on phones. if renpy.variant("small"): ## Font sizes. gui.text_size = 30 gui.name_text_size = 36 gui.notify_text_size = 25 gui.interface_text_size = 36 gui.button_text_size = 34 gui.label_text_size = 36 ## Adjust the location of the textbox. gui.textbox_height = 240 gui.name_xpos = 80 gui.text_xpos = 90 gui.text_width = 1100 ## Change the size and spacing of items in the game menu. gui.choice_button_width = 1240 gui.navigation_spacing = 20 gui.pref_button_spacing = 10 gui.history_height = 190 gui.history_text_width = 690 ## File button layout. gui.file_slot_cols = 2 gui.file_slot_rows = 2 ## NVL-mode. gui.nvl_height = 170 gui.nvl_name_width = 305 gui.nvl_name_xpos = 325 gui.nvl_text_width = 915 gui.nvl_text_xpos = 345 gui.nvl_text_ypos = 5 gui.nvl_thought_width = 1240 gui.nvl_thought_xpos = 20 gui.nvl_button_width = 1240 gui.nvl_button_xpos = 20 ## Quick buttons. gui.quick_button_text_size = 20
In the options code, I commented out the ‘version’ number because it didn’t matter for now. I also added text for the ‘About’ menu, and first experimented adding sound here.
This is the options code:
## This file contains options that can be changed to customize your game. ## ## Lines beginning with two '#' marks are comments, and you shouldn't uncomment ## them. Lines beginning with a single '#' mark are commented-out code, and you ## may want to uncomment them when appropriate. ## Basics ###################################################################### ## config.mouse = { 'default' : [ ('pointinghand.jpg', 0, 0)] } ## A human-readable name of the game. This is used to set the default window ## title, and shows up in the interface and error reports. ## ## The _() surrounding the string marks it as eligible for translation. define config.name = _("The Grind") ## Determines if the title given above is shown on the main menu screen. Set ## this to False to hide the title. define gui.show_name = True ## The version of the game. ## define config.version = "1.0" ## Text that is placed on the game's about screen. To insert a blank line ## between paragraphs, write \n\n. define gui.about = _("This is a prologue to a larger visual novel that may be produced later on. This was made by Ferwa Razzaq, an Interactive Media Arts student at NYU Shanghai. April 2017") ## A short name for the game used for executables and directories in the built ## distribution. This must be ASCII-only, and must not contain spaces, colons, ## or semicolons. define build.name = "TheGrind" ## Sounds and music ############################################################ ## These three variables control which mixers are shown to the player by ## default. Setting one of these to False will hide the appropriate mixer. define config.has_sound = True define config.has_music = True define config.has_voice = True ## To allow the user to play a test sound on the sound or voice channel, ## uncomment a line below and use it to set a sample sound to play. # define config.sample_sound = "sample-sound.ogg" # define config.sample_voice = "sample-voice.ogg" ## Uncomment the following line to set an audio file that will be played while ## the player is at the main menu. This file will continue playing into the ## game, until it is stopped or another file is played. ## define config.main_menu_music = "ProfessorLaytonPuzzlesSuperExtended.mp3" ## Transitions ################################################################# ## ## These variables set transitions that are used when certain events occur. ## Each variable should be set to a transition, or None to indicate that no ## transition should be used. ## Entering or exiting the game menu. define config.enter_transition = dissolve define config.exit_transition = dissolve ## A transition that is used after a game has been loaded. define config.after_load_transition = None ## Used when entering the main menu after the game has ended. define config.end_game_transition = fade ## A variable to set the transition used when the game starts does not exist. ## Instead, use a with statement after showing the initial scene. ## Window management ########################################################### ## ## This controls when the dialogue window is displayed. If "show", it is always ## displayed. If "hide", it is only displayed when dialogue is present. If ## "auto", the window is hidden before scene statements and shown again once ## dialogue is displayed. ## ## After the game has started, this can be changed with the "window show", ## "window hide", and "window auto" statements. define config.window = "auto" ## Transitions used to show and hide the dialogue window define config.window_show_transition = Dissolve(.2) define config.window_hide_transition = Dissolve(.2) ## Preference defaults ######################################################### ## Controls the default text speed. The default, 0, is infinite, while any other ## number is the number of characters per second to type out. default preferences.text_cps = 0 ## The default auto-forward delay. Larger numbers lead to longer waits, with 0 ## to 30 being the valid range. default preferences.afm_time = 15 ## Save directory ############################################################## ## ## Controls the platform-specific place Ren'Py will place the save files for ## this game. The save files will be placed in: ## ## Windows: %APPDATA\RenPy\ ## ## Macintosh: $HOME/Library/RenPy/ ## ## Linux: $HOME/.renpy/ ## ## This generally should not be changed, and if it is, should always be a ## literal string, not an expression. define config.save_directory = "TheGrind-1489760294" ## Icon ######################################################################## ## ## The icon displayed on the taskbar or dock. define config.window_icon = "gui/window_icon.png" ## Build configuration ######################################################### ## ## This section controls how Ren'Py turns your project into distribution files. init python: ## The following functions take file patterns. File patterns are case- ## insensitive, and matched against the path relative to the base directory, ## with and without a leading /. If multiple patterns match, the first is ## used. ## ## In a pattern: ## ## / is the directory separator. ## ## * matches all characters, except the directory separator. ## ## ** matches all characters, including the directory separator. ## ## For example, "*.txt" matches txt files in the base directory, "game/ ## **.ogg" matches ogg files in the game directory or any of its ## subdirectories, and "**.psd" matches psd files anywhere in the project. ## Classify files as None to exclude them from the built distributions. build.classify('**~', None) build.classify('**.bak', None) build.classify('**/.**', None) build.classify('**/#**', None) build.classify('**/thumbs.db', None) ## To archive files, classify them as 'archive'. # build.classify('game/**.png', 'archive') # build.classify('game/**.jpg', 'archive') ## Files matching documentation patterns are duplicated in a mac app build, ## so they appear in both the app and the zip file. build.documentation('*.html') build.documentation('*.txt') ## A Google Play license key is required to download expansion files and perform ## in-app purchases. It can be found on the "Services & APIs" page of the Google ## Play developer console. # define build.google_play_key = "..." ## The username and project name associated with an itch.io project, separated ## by a slash. # define build.itch_project = "renpytom/test-project"
In the screens code, I edited the position of the title on the main menu and made sure things were aligned properly.
And this is the screens code:
################################################################################ ## Initialization ################################################################################ init offset = -1 ################################################################################ ## Styles ################################################################################ style default: properties gui.text_properties() language gui.language style input: properties gui.text_properties("input", accent=True) adjust_spacing False style hyperlink_text: properties gui.text_properties("hyperlink", accent=True) hover_underline True style gui_text: properties gui.text_properties("interface") style button: properties gui.button_properties("button") style button_text is gui_text: properties gui.text_properties("button") yalign 0.5 style label_text is gui_text: properties gui.text_properties("label", accent=True) style prompt_text is gui_text: properties gui.text_properties("prompt") style bar: ysize gui.bar_size left_bar Frame("gui/bar/left.png", gui.bar_borders, tile=gui.bar_tile) right_bar Frame("gui/bar/right.png", gui.bar_borders, tile=gui.bar_tile) style vbar: xsize gui.bar_size top_bar Frame("gui/bar/top.png", gui.vbar_borders, tile=gui.bar_tile) bottom_bar Frame("gui/bar/bottom.png", gui.vbar_borders, tile=gui.bar_tile) style scrollbar: ysize gui.scrollbar_size base_bar Frame("gui/scrollbar/horizontal_[prefix_]bar.png", gui.scrollbar_borders, tile=gui.scrollbar_tile) thumb Frame("gui/scrollbar/horizontal_[prefix_]thumb.png", gui.scrollbar_borders, tile=gui.scrollbar_tile) style vscrollbar: xsize gui.scrollbar_size base_bar Frame("gui/scrollbar/vertical_[prefix_]bar.png", gui.vscrollbar_borders, tile=gui.scrollbar_tile) thumb Frame("gui/scrollbar/vertical_[prefix_]thumb.png", gui.vscrollbar_borders, tile=gui.scrollbar_tile) style slider: ysize gui.slider_size base_bar Frame("gui/slider/horizontal_[prefix_]bar.png", gui.slider_borders, tile=gui.slider_tile) thumb "gui/slider/horizontal_[prefix_]thumb.png" style vslider: xsize gui.slider_size base_bar Frame("gui/slider/vertical_[prefix_]bar.png", gui.vslider_borders, tile=gui.slider_tile) thumb "gui/slider/vertical_[prefix_]thumb.png" style frame: padding gui.frame_borders.padding background Frame("gui/frame.png", gui.frame_borders, tile=gui.frame_tile) ################################################################################ ## In-game screens ################################################################################ ## Say screen ################################################################## ## ## The say screen is used to display dialogue to the player. It takes two ## parameters, who and what, which are the name of the speaking character and ## the text to be displayed, respectively. (The who parameter can be None if no ## name is given.) ## ## This screen must create a text displayable with id "what", as Ren'Py uses ## this to manage text display. It can also create displayables with id "who" ## and id "window" to apply style properties. ## ## https://www.renpy.org/doc/html/screen_special.html#say screen say(who, what): style_prefix "say" window: id "window" if who is not None: window: style "namebox" text who id "who" text what id "what" ## If there's a side image, display it above the text. Do not display on the ## phone variant - there's no room. if not renpy.variant("small"): add SideImage() xalign 0.0 yalign 1.0 style window is default style say_label is default style say_dialogue is default style say_thought is say_dialogue style namebox is default style namebox_label is say_label style window: xalign 0.5 xfill True yalign gui.textbox_yalign ysize gui.textbox_height background Image("gui/textbox.png", xalign=0.5, yalign=1.0) style namebox: xpos gui.name_xpos xanchor gui.name_xalign xsize gui.namebox_width ypos gui.name_ypos ysize gui.namebox_height background Frame("gui/namebox.png", gui.namebox_borders, tile=gui.namebox_tile, xalign=gui.name_xalign) padding gui.namebox_borders.padding style say_label: properties gui.text_properties("name", accent=True) xalign gui.name_xalign yalign 0.5 style say_dialogue: properties gui.text_properties("dialogue") xpos gui.dialogue_xpos xsize gui.dialogue_width ypos gui.dialogue_ypos ## Input screen ################################################################ ## ## This screen is used to display renpy.input. The prompt parameter is used to ## pass a text prompt in. ## ## This screen must create an input displayable with id "input" to accept the ## various input parameters. ## ## http://www.renpy.org/doc/html/screen_special.html#input screen input(prompt): style_prefix "input" window: vbox: xalign gui.dialogue_text_xalign xpos gui.dialogue_xpos xsize gui.dialogue_width ypos gui.dialogue_ypos text prompt style "input_prompt" input id "input" style input_prompt is default style input_prompt: xalign gui.dialogue_text_xalign properties gui.text_properties("input_prompt") style input: xalign gui.dialogue_text_xalign xmaximum gui.dialogue_width ## Choice screen ############################################################### ## ## This screen is used to display the in-game choices presented by the menu ## statement. The one parameter, items, is a list of objects, each with caption ## and action fields. ## ## http://www.renpy.org/doc/html/screen_special.html#choice screen choice(items): style_prefix "choice" vbox: for i in items: textbutton i.caption action i.action ## When this is true, menu captions will be spoken by the narrator. When false, ## menu captions will be displayed as empty buttons. define config.narrator_menu = True style choice_vbox is vbox style choice_button is button style choice_button_text is button_text style choice_vbox: xalign 0.5 ypos 270 yanchor 0.5 spacing gui.choice_spacing style choice_button is default: properties gui.button_properties("choice_button") style choice_button_text is default: properties gui.button_text_properties("choice_button") ## Quick Menu screen ########################################################### ## ## The quick menu is displayed in-game to provide easy access to the out-of-game ## menus. screen quick_menu(): ## Ensure this appears on top of other screens. zorder 100 if quick_menu: hbox: style_prefix "quick" xalign 0.5 yalign 1.0 textbutton _("Back") action Rollback() textbutton _("History") action ShowMenu('history') textbutton _("Skip") action Skip() alternate Skip(fast=True, confirm=True) textbutton _("Auto") action Preference("auto-forward", "toggle") textbutton _("Save") action ShowMenu('save') textbutton _("Q.Save") action QuickSave() textbutton _("Q.Load") action QuickLoad() textbutton _("Prefs") action ShowMenu('preferences') ## This code ensures that the quick_menu screen is displayed in-game, whenever ## the player has not explicitly hidden the interface. init python: config.overlay_screens.append("quick_menu") default quick_menu = True style quick_button is default style quick_button_text is button_text style quick_button: properties gui.button_properties("quick_button") style quick_button_text: properties gui.button_text_properties("quick_button") ################################################################################ ## Main and Game Menu Screens ################################################################################ ## Navigation screen ########################################################### ## ## This screen is included in the main and game menus, and provides navigation ## to other menus, and to start the game. screen navigation(): vbox: style_prefix "navigation" xpos gui.navigation_xpos yalign 0.5 spacing gui.navigation_spacing if main_menu: textbutton _("Start") action Start() else: textbutton _("History") action ShowMenu("history") textbutton _("Save") action ShowMenu("save") textbutton _("Load") action ShowMenu("load") textbutton _("Preferences") action ShowMenu("preferences") if _in_replay: textbutton _("End Replay") action EndReplay(confirm=True) elif not main_menu: textbutton _("Main Menu") action MainMenu() textbutton _("About") action ShowMenu("about") if renpy.variant("pc"): ## Help isn't necessary or relevant to mobile devices. textbutton _("Help") action ShowMenu("help") ## The quit button is banned on iOS and unnecessary on Android. textbutton _("Quit") action Quit(confirm=not main_menu) style navigation_button is gui_button style navigation_button_text is gui_button_text style navigation_button: size_group "navigation" properties gui.button_properties("navigation_button") style navigation_button_text: properties gui.button_text_properties("navigation_button") ## Main Menu screen ############################################################ ## ## Used to display the main menu when Ren'Py starts. ## ## http://www.renpy.org/doc/html/screen_special.html#main-menu screen main_menu(): ## This ensures that any other menu screen is replaced. tag menu style_prefix "main_menu" add gui.main_menu_background ## This empty frame darkens the main menu. frame: pass ## The use statement includes another screen inside this one. The actual ## contents of the main menu are in the navigation screen. use navigation if gui.show_name: vbox: text "[config.name!t]": style "main_menu_title" text "[config.version]": style "main_menu_version" style main_menu_frame is empty style main_menu_vbox is vbox style main_menu_text is gui_text style main_menu_title is main_menu_text style main_menu_version is main_menu_text style main_menu_frame: xsize 280 yfill True ## background "gui/overlay/main_menu.png" style main_menu_vbox: xalign 0.87 xoffset -20 xmaximum 800 yalign 0.18 yoffset -20 style main_menu_text: properties gui.text_properties("main_menu", accent=True) style main_menu_title: properties gui.text_properties("title") style main_menu_version: properties gui.text_properties("version") ## Game Menu screen ############################################################ ## ## This lays out the basic common structure of a game menu screen. It's called ## with the screen title, and displays the background, title, and navigation. ## ## The scroll parameter can be None, or one of "viewport" or "vpgrid". When ## this screen is intended to be used with one or more children, which are ## transcluded (placed) inside it. screen game_menu(title, scroll=None): style_prefix "game_menu" if main_menu: add gui.main_menu_background else: add gui.game_menu_background frame: style "game_menu_outer_frame" hbox: ## Reserve space for the navigation section. frame: style "game_menu_navigation_frame" frame: style "game_menu_content_frame" if scroll == "viewport": viewport: scrollbars "vertical" mousewheel True draggable True side_yfill True vbox: transclude elif scroll == "vpgrid": vpgrid: cols 1 yinitial 1.0 scrollbars "vertical" mousewheel True draggable True side_yfill True transclude else: transclude use navigation textbutton _("Return"): style "return_button" action Return() label title if main_menu: key "game_menu" action ShowMenu("main_menu") style game_menu_outer_frame is empty style game_menu_navigation_frame is empty style game_menu_content_frame is empty style game_menu_viewport is gui_viewport style game_menu_side is gui_side style game_menu_scrollbar is gui_vscrollbar style game_menu_label is gui_label style game_menu_label_text is gui_label_text style return_button is navigation_button style return_button_text is navigation_button_text style game_menu_outer_frame: bottom_padding 30 top_padding 120 ## background "gui/overlay/game_menu.png" style game_menu_navigation_frame: xsize 280 yfill True style game_menu_content_frame: left_margin 40 right_margin 20 top_margin 10 style game_menu_viewport: xsize 920 style game_menu_vscrollbar: unscrollable gui.unscrollable style game_menu_side: spacing 10 style game_menu_label: xpos 50 ysize 120 style game_menu_label_text: size gui.title_text_size color gui.accent_color yalign 0.5 style return_button: xpos gui.navigation_xpos yalign 1.0 yoffset -30 ## About screen ################################################################ ## ## This screen gives credit and copyright information about the game and Ren'Py. ## ## There's nothing special about this screen, and hence it also serves as an ## example of how to make a custom screen. screen about(): tag menu ## This use statement includes the game_menu screen inside this one. The ## vbox child is then included inside the viewport inside the game_menu ## screen. use game_menu(_("About"), scroll="viewport"): style_prefix "about" vbox: label "[config.name!t]" text _("Version [config.version!t]\n") ## gui.about is usually set in options.rpy. if gui.about: text "[gui.about!t]\n" text _("Made with {a=https://www.renpy.org/}Ren'Py{/a} [renpy.version_only].\n\n[renpy.license!t]") ## This is redefined in options.rpy to add text to the about screen. define gui.about = "" style about_label is gui_label style about_label_text is gui_label_text style about_text is gui_text style about_label_text: size gui.label_text_size ## Load and Save screens ####################################################### ## ## These screens are responsible for letting the player save the game and load ## it again. Since they share nearly everything in common, both are implemented ## in terms of a third screen, file_slots. ## ## https://www.renpy.org/doc/html/screen_special.html#save https:// ## www.renpy.org/doc/html/screen_special.html#load screen save(): tag menu use file_slots(_("Save")) screen load(): tag menu use file_slots(_("Load")) screen file_slots(title): default page_name_value = FilePageNameInputValue(pattern=_("Page {}"), auto=_("Automatic saves"), quick=_("Quick saves")) use game_menu(title): fixed: ## This ensures the input will get the enter event before any of the ## buttons do. order_reverse True ## The page name, which can be edited by clicking on a button. button: style "page_label" key_events True xalign 0.5 action page_name_value.Toggle() input: style "page_label_text" value page_name_value ## The grid of file slots. grid gui.file_slot_cols gui.file_slot_rows: style_prefix "slot" xalign 0.5 yalign 0.5 spacing gui.slot_spacing for i in range(gui.file_slot_cols * gui.file_slot_rows): $ slot = i + 1 button: action FileAction(slot) has vbox add FileScreenshot(slot) xalign 0.5 text FileTime(slot, format=_("{#file_time}%A, %B %d %Y, %H:%M"), empty=_("empty slot")): style "slot_time_text" text FileSaveName(slot): style "slot_name_text" key "save_delete" action FileDelete(slot) ## Buttons to access other pages. hbox: style_prefix "page" xalign 0.5 yalign 1.0 spacing gui.page_spacing textbutton _("") action FilePageNext() style page_label is gui_label style page_label_text is gui_label_text style page_button is gui_button style page_button_text is gui_button_text style slot_button is gui_button style slot_button_text is gui_button_text style slot_time_text is slot_button_text style slot_name_text is slot_button_text style page_label: xpadding 50 ypadding 3 style page_label_text: text_align 0.5 layout "subtitle" hover_color gui.hover_color style page_button: properties gui.button_properties("page_button") style page_button_text: properties gui.button_text_properties("page_button") style slot_button: properties gui.button_properties("slot_button") style slot_button_text: properties gui.button_text_properties("slot_button") ## Preferences screen ########################################################## ## ## The preferences screen allows the player to configure the game to better suit ## themselves. ## ## https://www.renpy.org/doc/html/screen_special.html#preferences screen preferences(): tag menu if renpy.mobile: $ cols = 2 else: $ cols = 4 use game_menu(_("Preferences"), scroll="viewport"): vbox: hbox: box_wrap True if renpy.variant("pc"): vbox: style_prefix "radio" label _("Display") textbutton _("Window") action Preference("display", "window") textbutton _("Fullscreen") action Preference("display", "fullscreen") vbox: style_prefix "radio" label _("Rollback Side") textbutton _("Disable") action Preference("rollback side", "disable") textbutton _("Left") action Preference("rollback side", "left") textbutton _("Right") action Preference("rollback side", "right") vbox: style_prefix "check" label _("Skip") textbutton _("Unseen Text") action Preference("skip", "toggle") textbutton _("After Choices") action Preference("after choices", "toggle") textbutton _("Transitions") action InvertSelected(Preference("transitions", "toggle")) ## Additional vboxes of type "radio_pref" or "check_pref" can be ## added here, to add additional creator-defined preferences. null height (4 * gui.pref_spacing) hbox: style_prefix "slider" box_wrap True vbox: label _("Text Speed") bar value Preference("text speed") label _("Auto-Forward Time") bar value Preference("auto-forward time") vbox: if config.has_music: label _("Music Volume") hbox: bar value Preference("music volume") if config.has_sound: label _("Sound Volume") hbox: bar value Preference("sound volume") if config.sample_sound: textbutton _("Test") action Play("sound", config.sample_sound) if config.has_voice: label _("Voice Volume") hbox: bar value Preference("voice volume") if config.sample_voice: textbutton _("Test") action Play("voice", config.sample_voice) if config.has_music or config.has_sound or config.has_voice: null height gui.pref_spacing textbutton _("Mute All"): action Preference("all mute", "toggle") style "mute_all_button" style pref_label is gui_label style pref_label_text is gui_label_text style pref_vbox is vbox style radio_label is pref_label style radio_label_text is pref_label_text style radio_button is gui_button style radio_button_text is gui_button_text style radio_vbox is pref_vbox style check_label is pref_label style check_label_text is pref_label_text style check_button is gui_button style check_button_text is gui_button_text style check_vbox is pref_vbox style slider_label is pref_label style slider_label_text is pref_label_text style slider_slider is gui_slider style slider_button is gui_button style slider_button_text is gui_button_text style slider_pref_vbox is pref_vbox style mute_all_button is check_button style mute_all_button_text is check_button_text style pref_label: top_margin gui.pref_spacing bottom_margin 2 style pref_label_text: yalign 1.0 style pref_vbox: xsize 225 style radio_vbox: spacing gui.pref_button_spacing style radio_button: properties gui.button_properties("radio_button") foreground "gui/button/check_[prefix_]foreground.png" style radio_button_text: properties gui.button_text_properties("radio_button") style check_vbox: spacing gui.pref_button_spacing style check_button: properties gui.button_properties("check_button") foreground "gui/button/check_[prefix_]foreground.png" style check_button_text: properties gui.button_text_properties("check_button") style slider_slider: xsize 350 style slider_button: properties gui.button_properties("slider_button") yalign 0.5 left_margin 10 style slider_button_text: properties gui.button_text_properties("slider_button") style slider_vbox: xsize 450 ## History screen ############################################################## ## ## This is a screen that displays the dialogue history to the player. While ## there isn't anything special about this screen, it does have to access the ## dialogue history stored in _history_list. ## ## https://www.renpy.org/doc/html/history.html screen history(): tag menu ## Avoid predicting this screen, as it can be very large. predict False use game_menu(_("History"), scroll=("vpgrid" if gui.history_height else "viewport")): style_prefix "history" for h in _history_list: window: ## This lays things out properly if history_height is None. has fixed: yfit True if h.who: label h.who: style "history_name" ## Take the color of the who text from the Character, if ## set. if "color" in h.who_args: text_color h.who_args["color"] text h.what if not _history_list: label _("The dialogue history is empty.") style history_window is empty style history_name is gui_label style history_name_text is gui_label_text style history_text is gui_text style history_text is gui_text style history_label is gui_label style history_label_text is gui_label_text style history_window: xfill True ysize gui.history_height style history_name: xpos gui.history_name_xpos xanchor gui.history_name_xalign ypos gui.history_name_ypos xsize gui.history_name_width style history_name_text: min_width gui.history_name_width text_align gui.history_name_xalign style history_text: xpos gui.history_text_xpos ypos gui.history_text_ypos xanchor gui.history_text_xalign xsize gui.history_text_width min_width gui.history_text_width text_align gui.history_text_xalign layout ("subtitle" if gui.history_text_xalign else "tex") style history_label: xfill True style history_label_text: xalign 0.5 ## Help screen ################################################################# ## ## A screen that gives information about key and mouse bindings. It uses other ## screens (keyboard_help, mouse_help, and gamepad_help) to display the actual ## help. screen help(): tag menu default device = "keyboard" use game_menu(_("Help"), scroll="viewport"): style_prefix "help" vbox: spacing 15 hbox: textbutton _("Keyboard") action SetScreenVariable("device", "keyboard") textbutton _("Mouse") action SetScreenVariable("device", "mouse") if GamepadExists(): textbutton _("Gamepad") action SetScreenVariable("device", "gamepad") if device == "keyboard": use keyboard_help elif device == "mouse": use mouse_help elif device == "gamepad": use gamepad_help screen keyboard_help(): hbox: label _("Enter") text _("Advances dialogue and activates the interface.") hbox: label _("Space") text _("Advances dialogue without selecting choices.") hbox: label _("Arrow Keys") text _("Navigate the interface.") hbox: label _("Escape") text _("Accesses the game menu.") hbox: label _("Ctrl") text _("Skips dialogue while held down.") hbox: label _("Tab") text _("Toggles dialogue skipping.") hbox: label _("Page Up") text _("Rolls back to earlier dialogue.") hbox: label _("Page Down") text _("Rolls forward to later dialogue.") hbox: label "H" text _("Hides the user interface.") hbox: label "S" text _("Takes a screenshot.") hbox: label "V" text _("Toggles assistive {a=https://www.renpy.org/l/voicing}self-voicing{/a}.") screen mouse_help(): hbox: label _("Left Click") text _("Advances dialogue and activates the interface.") hbox: label _("Middle Click") text _("Hides the user interface.") hbox: label _("Right Click") text _("Accesses the game menu.") hbox: label _("Mouse Wheel Up\nClick Rollback Side") text _("Rolls back to earlier dialogue.") hbox: label _("Mouse Wheel Down") text _("Rolls forward to later dialogue.") screen gamepad_help(): hbox: label _("Right Trigger\nA/Bottom Button") text _("Advances dialogue and activates the interface.") hbox: label _("Left Trigger\nLeft Shoulder") text _("Rolls back to earlier dialogue.") hbox: label _("Right Shoulder") text _("Rolls forward to later dialogue.") hbox: label _("D-Pad, Sticks") text _("Navigate the interface.") hbox: label _("Start, Guide") text _("Accesses the game menu.") hbox: label _("Y/Top Button") text _("Hides the user interface.") textbutton _("Calibrate") action GamepadCalibrate() style help_button is gui_button style help_button_text is gui_button_text style help_label is gui_label style help_label_text is gui_label_text style help_text is gui_text style help_button: properties gui.button_properties("help_button") xmargin 8 style help_button_text: properties gui.button_text_properties("help_button") style help_label: xsize 250 right_padding 20 style help_label_text: size gui.text_size xalign 1.0 text_align 1.0 ################################################################################ ## Additional screens ################################################################################ ## Confirm screen ############################################################## ## ## The confirm screen is called when Ren'Py wants to ask the player a yes or no ## question. ## ## http://www.renpy.org/doc/html/screen_special.html#confirm screen confirm(message, yes_action, no_action): ## Ensure other screens do not get input while this screen is displayed. modal True zorder 200 style_prefix "confirm" add "gui/overlay/confirm.png" frame: vbox: xalign .5 yalign .5 spacing 30 label _(message): style "confirm_prompt" xalign 0.5 hbox: xalign 0.5 spacing 100 textbutton _("Yes") action yes_action textbutton _("No") action no_action ## Right-click and escape answer "no". key "game_menu" action no_action style confirm_frame is gui_frame style confirm_prompt is gui_prompt style confirm_prompt_text is gui_prompt_text style confirm_button is gui_medium_button style confirm_button_text is gui_medium_button_text style confirm_frame: background Frame([ "gui/confirm_frame.png", "gui/frame.png"], gui.confirm_frame_borders, tile=gui.frame_tile) padding gui.confirm_frame_borders.padding xalign .5 yalign .5 style confirm_prompt_text: text_align 0.5 layout "subtitle" style confirm_button: properties gui.button_properties("confirm_button") style confirm_button_text: properties gui.button_text_properties("confirm_button") ## Skip indicator screen ####################################################### ## ## The skip_indicator screen is displayed to indicate that skipping is in ## progress. ## ## https://www.renpy.org/doc/html/screen_special.html#skip-indicator screen skip_indicator(): zorder 100 style_prefix "skip" frame: hbox: spacing 6 text _("Skipping") text "▸" at delayed_blink(0.0, 1.0) style "skip_triangle" text "▸" at delayed_blink(0.2, 1.0) style "skip_triangle" text "▸" at delayed_blink(0.4, 1.0) style "skip_triangle" ## This transform is used to blink the arrows one after another. transform delayed_blink(delay, cycle): alpha .5 pause delay block: linear .2 alpha 1.0 pause .2 linear .2 alpha 0.5 pause (cycle - .4) repeat style skip_frame is empty style skip_text is gui_text style skip_triangle is skip_text style skip_frame: ypos gui.skip_ypos background Frame("gui/skip.png", gui.skip_frame_borders, tile=gui.frame_tile) padding gui.skip_frame_borders.padding style skip_text: size gui.notify_text_size style skip_triangle: ## We have to use a font that has the BLACK RIGHT-POINTING SMALL TRIANGLE ## glyph in it. font "DejaVuSans.ttf" ## Notify screen ############################################################### ## ## The notify screen is used to show the player a message. (For example, when ## the game is quicksaved or a screenshot has been taken.) ## ## https://www.renpy.org/doc/html/screen_special.html#notify-screen screen notify(message): zorder 100 style_prefix "notify" frame at notify_appear: text message timer 3.25 action Hide('notify') transform notify_appear: on show: alpha 0 linear .25 alpha 1.0 on hide: linear .5 alpha 0.0 style notify_frame is empty style notify_text is gui_text style notify_frame: ypos gui.notify_ypos background Frame("gui/notify.png", gui.notify_frame_borders, tile=gui.frame_tile) padding gui.notify_frame_borders.padding style notify_text: properties gui.text_properties("notify") ## NVL screen ################################################################## ## ## This screen is used for NVL-mode dialogue and menus. ## ## http://www.renpy.org/doc/html/screen_special.html#nvl screen nvl(dialogue, items=None): window: style "nvl_window" has vbox: spacing gui.nvl_spacing ## Displays dialogue in either a vpgrid or the vbox. if gui.nvl_height: vpgrid: cols 1 yinitial 1.0 use nvl_dialogue(dialogue) else: use nvl_dialogue(dialogue) ## Displays the menu, if given. The menu may be displayed incorrectly if ## config.narrator_menu is set to True, as it is above. for i in items: textbutton i.caption: action i.action style "nvl_button" add SideImage() xalign 0.0 yalign 1.0 screen nvl_dialogue(dialogue): for d in dialogue: window: id d.window_id fixed: yfit gui.nvl_height is None if d.who is not None: text d.who: id d.who_id text d.what: id d.what_id ## This controls the maximum number of NVL-mode entries that can be displayed at ## once. define config.nvl_list_length = 6 style nvl_window is default style nvl_entry is default style nvl_label is say_label style nvl_dialogue is say_dialogue style nvl_button is button style nvl_button_text is button_text style nvl_window: xfill True yfill True background "gui/nvl.png" padding gui.nvl_borders.padding style nvl_entry: xfill True ysize gui.nvl_height style nvl_label: xpos gui.nvl_name_xpos xanchor gui.nvl_name_xalign ypos gui.nvl_name_ypos yanchor 0.0 xsize gui.nvl_name_width min_width gui.nvl_name_width text_align gui.nvl_name_xalign style nvl_dialogue: xpos gui.nvl_text_xpos xanchor gui.nvl_text_xalign ypos gui.nvl_text_ypos xsize gui.nvl_text_width min_width gui.nvl_text_width text_align gui.nvl_text_xalign layout ("subtitle" if gui.nvl_text_xalign else "tex") style nvl_thought: xpos gui.nvl_thought_xpos xanchor gui.nvl_thought_xalign ypos gui.nvl_thought_ypos xsize gui.nvl_thought_width min_width gui.nvl_thought_width text_align gui.nvl_thought_xalign layout ("subtitle" if gui.nvl_text_xalign else "tex") style nvl_button: properties gui.button_properties("nvl_button") xpos gui.nvl_button_xpos xanchor gui.nvl_button_xalign style nvl_button_text: properties gui.button_text_properties("nvl_button") ################################################################################ ## Mobile Variants ################################################################################ style pref_vbox: variant "medium" xsize 450 ## Since a mouse may not be present, we replace the quick menu with a version ## that uses fewer and bigger buttons that are easier to touch. screen quick_menu(): variant "touch" zorder 100 hbox: style_prefix "quick" xalign 0.5 yalign 1.0 textbutton _("Back") action Rollback() textbutton _("Skip") action Skip() alternate Skip(fast=True, confirm=True) textbutton _("Auto") action Preference("auto-forward", "toggle") textbutton _("Menu") action ShowMenu() style window: variant "small" background "gui/phone/textbox.png" style nvl_window: variant "small" background "gui/phone/nvl.png" style main_menu_frame: variant "small" background "gui/phone/overlay/main_menu.png" style game_menu_outer_frame: variant "small" background "gui/phone/overlay/game_menu.png" style game_menu_navigation_frame: variant "small" xsize 340 style game_menu_content_frame: variant "small" top_margin 0 style pref_vbox: variant "small" xsize 400 style slider_pref_vbox: variant "small" xsize None style slider_pref_slider: variant "small" xsize 600
USER TESTING & ADJUSTMENTS
During user testing, people seemed pretty satisfied with the way the project was running. I was surprised to find that people liked the texture of the rice paper that came through when photographing the illustrations with my phone’s camera. I’d actually planned on scanning the illustrations at first, but captured a couple of them with my phone so that I could add them to my script code more quickly for user testing. After the user feedback, I decided to continue taking pictures of the illustrations with my phone. As mentioned before, I’d then edit them on BeFunky.
Another piece of feedback I was given was to adjust the text colors on the About, History and Preference menus in the game, because the text was showing up white, which was hard to read. Strangely enough, this took me a long time to figure out how to fix. Frustratingly, as these things go, the solution was a simple hex color change in the GUI code file.
Besides this, it was also brought to my attention that the confirmation boxes needed editing; for example, when going to the main menu, the game asks for confirmation about whether you really want to go there with yes or no options to answer with. Except there were no questions – just yes or no options. It turned out the problem was the white text as mentioned above – the text was there, it was just unreadable.
During user testing, Professor Anna Greenspan questioned whether the choices were coming in too late. I explained that in visual novels, there aren’t too many choices, nor they do they come too often, especially considering my project was only a prologue, for reasons I explained near the beginning of this post. Too many branches and choices affect the narrative. The classmates who were user testing at the time, Michael Chang and Shirley Huang, who both seemed to have some experience with visual novels as well, agreed that the pacing seemed fine. The first few choices don’t branch the story; they’re there to get the reader into the habit of making choices. Then there is a choice near the end of the story that splits it into three paths. Professor Greenspan questioned whether it was clear that the story takes different directions, and I think that if somebody actually goes through the whole story and repeats it with the paths I’ve set out properly, instead of simply skimming it, then it will be very clear that they are different.
During my capstone presentation, Professor Marianne Petite pointed out to me that the font detracted from the overall look of the project. I’d initially chosen the font Garamond because I liked the look of it with the rest of the work, but of course I took the feedback into account and considered it would be a good idea to change it. I then searched for and found an appropriate brush-style font called A Quiet Sleep on DaFont and replaced Garamond with that in my code.
PDF & PAPER & POWERPOINT
Here’s the PDF version of my project, with some new edits (click download anyway):
https://drive.google.com/file/d/0B11OZTl6J7J9aWRuNFNaaExtOFE/view?usp=sharing
Here’s my capstone research paper:
https://drive.google.com/file/d/0B11OZTl6J7J9VjFqMWthTmViQjA/view?usp=sharing
Here’s the PowerPoint of my capstone presentation:
https://docs.google.com/presentation/d/1Vd6URxhxDSxlRUzBAQCk1NIL9y0JzoUHfQW9RsdPOl4/edit?usp=sharing
ACKNOWLEDGMENTS
Ren’Py visual novel engine with its new project template and documentation.
Lemma Soft Forums for helping me find answers to issues I ran into or help I wanted to find.
Professor Layton and the Curious Village soundtrack Puzzles, composed by Tomohito Nishiura.
Thank you to Professor Barbara Edelstein for supplying me with rice paper!
Thanks to the IMA faculty that helped and gave me guidance while doing this project.