Home Artists Posts Import Register

Content

I thought I'd do a little explanation on how I put a scene together in Rogue-Like, and maybe inspire some of the community to get involved, either in working on this project, or just in making your own games, because Renpy is a really simple system to use, and much less intimidating than most people would think.

My first step is that I'll just start writing. Easy! I'll just start writing out a scene, in a linear format, just dialog, and I'll have an idea in mind for where I want the scene to go, but I just let it flow back and forth naturally for a while. I'll give a little example below. Note, anything that comes after a "#" in Renpy is considered a "comment," and is ignored by the game. This way you can leave notes for yourself so you don't forget what something does.

"Rogue walks into the room"
ch_r "Hey, [Playername]."
ch_p "Hey, Rogue."

Anything in quotes like this is presented as a narration block.   anything in brackets is a variable, in this case, slotting in the name  "ch_r" means Rogue is speaking, ch_p is the player, etc. This is my own naming scheme, other games can do this differently

I try to avoid putting too many words directly into the player's mouth, mostly non-controversial options like "Hey, Rogue." More often, when it's the player's turn, I like to provide a menu of multiple options. When I'm first writing out the scene, I might limit this menu to just raw dialog, and then go back later to add more detail to the options, so let's scrub out that "hey Rogue," and give some more options instead.

ch_r "Hey, [Playername]."
menu:
   extend ""
  "Hey Rogue!":
        pass
  "What?":
        ch_r "I said \"hey!\""
  "Go away!":
        ch_r "Rude!"
ch_r "So anyway. . ."

Ok, that's a fairly simple menu, let me explain what's going on there. Renpy and its underlying system "Python" is based on hat's called "blocks," basically every time you indent, everything that shares that indent is connected to the previous indentation. By that I mean that in this case, everything that is indented further back than "menu" is considered to be connected to that "menu" command, which will bring up an option of choices. Once we get down to that "so anyway" line, and it's no longer indented, that bit and anything after it won't be considered part of the menu. Script editing programs tend to make using tab to indent a very simple process (the Patreon post editor is a bit sloppier).

Below the menu:, we have a "header line," which is not a choice in the menu, it's something posted as text while the menu is up. You can put a question here, asking for an answer, or leave it blank, but extend "" will just copy the last thing said and leave it up, so when this menu displays, it will keep Rogue's last line showing while you pick your response.

So in this case I've offered three possible responses. Three is a nice round number, but of course you can give any amount. Responses have to be in quotes, with a colon at the end of the line. Then "what happens when they pick that answer" has to be intended one further gap deeper than the choice.

The first answer, the consequence of the choice is a "pass" statement. You can't just leave this section blank, there needs to be something there, so if you don't have anything to put there, you can just say "pass" and that will just kick the result down to "so anyway." You don't need a pass if anything else is happening there though. In rogue's statement "I said \"hey!\"", what I'm showing is that you can't just drop quotation marks inside of a quote, because the game would treat that as two separate blocks of text, on the same line, with a word in the middle, and that would confuse the hell out of it. Putting \ in front of the " means "ignore this in terms of code language, just pretend it's a normal "." You need to do this before each quotation mark you want to use like this. You can also use this trick on Reddit.

Now, what if you want to make the menus a bit more complicated? Let's just isolate one of those menu items:


    "What?" if PlayerStupid:
        if ApprovalCheck("Rogue", 600):
              ch_r "I said \"hey!\""
        else:
              call Statup("Rogue", "Love", 80, -2)
              call AnyFace("Rogue","angry")
              ch_r "You know what? Never mind."  

Ok, so what's happening with that mess? It's really pretty simple and a lot of it just gets cut and pasted throughout the document. Remember what I said about "just write the story out linearly," and then go back through and add stuff like this after you've done that.

So what's happening differently here is that the "what?" question will only appear if the variable "PlayerStupid" is "true." This isn't a real variable in the game, I'm just using it as an example, but you can put any sort of conditions here to hide various potential choices if they might not always make sense. So for example, if a third character might be in the room, you could make an option addressing them conditional in this way. If not "PlayerStupid," then only the other two options would show.

Next, after you've picked that answer, there is a conditional check. If the first part is "true," then do the part right under it. If it isn't, do the other thing. "ApprovalCheck" is a function I made for the game, and it's basically used to check whether a certain stat threshold gets met. This is the simplest version of it, in this case checking whether the character "Rogue" has at least 600 stat points when all three of her base stats are combined. I can get into more complex uses of this function later, but for this example, we just know that if she doesn't totally hate you, then she will just roll with your answer, no harm done. If the check fails, however. . .

The first thing it does is run the "Statup" function.  This is another tool I built, and it basically raises and lowers stats by a given amount. There are other ways to do this, but this way you also get those nifty floating numbers. In this case, it will take the character "Rogue"'s "Love" stat, and IF that stat is below 800, then it will lower it by 2. Why does it say 80 instead of 800? It's a holdover from the earliest versions of the game where stats capped off at 100. ;) Doesn't cause any harm though so I leave it alone.

Next we have a facial animation change. In this case, applying an "angry" face to "Rogue." There are also additional ways to make that more complex, adjusting the blush, or replacing various elements of the base expression individually. For example, call AnyFace("Rogue","angry",2,Eyes="side")  would take the default "angry" expression for her, make her blush furiously (the "2" could be a 1 or 0 to display less or no blushing), and then the "Eyes="side" would replace the default eyes for that expression with the sideways glance. This is also a unique function that I built for the game, but once set up it's quite easy to use.

I feel like that got a little complicated, so hopefully you'll have some questions I could answer, and we can digest that one a bit, but the main advice is, write the story down in a linear manner, and then go back into it and add things like conditions, consequences, stat changes, facial expressions, etc. Don't get too lost in the weeds right at the start of the scene. 

Comments

Anonymous

Just gotta say this is a really cool post for someone like yourself to do. Thanks!

Anonymous

Ok! This is epic!

Anonymous

Really love the behind the scenes insight here. I'm looking forward to working with Renpy in the future, so I really appreciate this!

Ai Muhao

This is all true. It's fairly easy to come up with scenes, and I seriously wish I just had time to sit down and work on scenes in more detail. And stop being so intimidated by all the lines.

Anonymous

I've been learning renpy for a month or so, I've got all the basics down. But how do you implement the approval rating system? That could be really helpful with what I'm working on.

OniArtist

Ok, that's not *too* complicated, but this is code-code stuff, so anyone reading this that just wants to be casually writing scenes, you won't need to know all of this. My current approval function was built up a bit over time and is about 150 lines, but here's the simplified version of it. First rule, it needs to be a "def," not a label, because otherwise you can't just slap it in the middle of a condition. That means it needs to be in one of those "init python" blocks, and so it won't have the $s. def ApprovalCheck(Chr = "Rogue", T = 1000, Type = "LOI", Bonus = 0, Check=0): # $ Count = ApprovalCheck("Rogue",125) # T is the value being checked against, Type is the LOI condition in play, Bonus is a random bonus applied, and Loc is the girl's location if Chr == "Rogue": #sets the data based on Rogue's data if Type == "X": #"X" is just used for Lust, if R_Lust >= T: #since there are less variables here, it just quick-returns return 1 else: return 0 L = R_Love O = R_Obed I = R_Inbt elif Chr == "Kitty": #etc. . . covers the other girls, same as with Rogue if Type == "LOI": LocalTaboo = Taboo * 10 elif Type == "LO": #40 -> 240 #culls unwanted parameters. I = 0 LocalTaboo = Taboo * 6 elif Type == "OI": L = 0 LocalTaboo = Taboo * 6 elif Type == "LI": O = 0 LocalTaboo = Taboo * 6 elif Type == "L": #40 -> 120 O = 0 I = 0 LocalTaboo = Taboo * 3 elif Type == "O": L = 0 I = 0 LocalTaboo = Taboo * 3 elif Type == "I": O = 0 L = 0 LocalTaboo = Taboo * 3 else: LocalTaboo = Taboo * 10 #40 -> 400 if Check: #this returns the actual value of the tested stat. Check = (L + O + I + Bonus - LocalTaboo) return Check if (L + O + I + Bonus - LocalTaboo) >= T: #She passes and loves it return 1 else: return 0 Ok, that's the end of it. So I removed some chunks of it that wouldn't be needed for a simple check, but basically it means that it establishes the base values in the first bit, then it determines which values you're testing against in the second bit, then if you wanted to just spit out what the result is (useful for comparing two values, but not really necessary without the extra stuff I cut out here), but finally it just adds everything up, and if it's higher than the check value, then it returns a 1, which makes that if condition true, otherwise it returns a 0, which makes that conditional "not true." So basically, if I did "if ApprovalCheck("Rogue",1250,"LI"):" then it would take in Rogue's stats, then it would zero out "O", her temporary obedience value, because in this test we're only testing her "L" and "I" values in this one. Make sure you're using temporary variables here because you don't want to accidentally overwrite her actual stats. Then in the next bit, it adds those up, subtracts the Taboo value (which is used for public stuff), checked it against 1250, and so basically if her Love and Inhibition stats add up to more than 1250, the condition passes.

Anonymous

Sweet thanks for explaining it! I'm going to mess around with the code right now

Anonymous

I'm not super familiar with renpy, but I do know python very well. Is there a reason why you don't use classes? if Chr == "Rogue": #sets the data based on Rogue's data if Type == "X": #"X" is just used for Lust, if R_Lust >= T: #since there are less variables here, it just quick-returns return 1 else: return 0 L = R_Love O = R_Obed I = R_Inbt elif Chr == "Kitty": # etc Seems like it could all get simplified if you had some kind of base class for a character: class XGirl: def __init__(self, love, inbt, obed): self.love = love self.inbt = inbt self.obed = obed # etc Then you wouldn't need such complex logic to decode "do I want R_Love, or E_Love, or K_Love, or L_Love, or ProfX_Love" you could just get Chr.Love It's a bit hard to discuss in-depth in a tiny comment window, I'm happy to discuss further in PMs if you'd like

OniArtist

I don't like classes. They don't make as much sense to me. This is definitely a "me" problem, and maybe eventually I'll get better at understanding how they work, but for the time being I prefer keeping things straightforward.

Krade Evvor

Lol, gamedev diary). Cool for diversity).

Patrick Thrasher

Lol, next update: nothing. Just streamlined all the code on the back end to provide more output in the future. :p I hate editing and redoing things, so I can relate if that never happens.

OniArtist

I always try to balance things out, I might do one major systems pass to make things simpler, but I always try to add at least a few brand new features as well. I have some vague goals for the next version, but while it will be a "broad but shallow" expansion, it will be more than just background stuff, it will be intended to have noticeable improvements to a lot of existing elements, and new features, just not an entirely new character yet.