RenPy Beginner - Questions

webmasterjunkie

Newbie
Game Developer
Jul 23, 2023
48
153
Hi All,

Working on whether or not I can create a VN. I am just running into things that don't make sense. Hoping this can be a place I ask some pretty basic questions and get some guidance on. Probably the first hiccup I am seeing is around OOP in my code. I am just working on functionality bits to ensure I can build the VN I am thinking about. I am creating an "Actor" class:

Code:
### Actor Object
init python:
    class Actor:
        def __init__(self, character, name, relationship, affection = 0):
            self.character = character
            self.name = name
            self.relationship = relationship

        def affectionUp(self, amount):
            self.affection += amount
            renpy.notify(self.name + " liked that!")

        def affectionDown(self, amount):
            self.affection -= amount
            renpy.notify(self.name + " will remember that!")
I have a pretty basic statement for it that looks like this:

Code:
define mel = Actor(
    Character("Melissa", who_color = "#213fe9"),
    "Melissa",
    "Neighbor"
    )
This all works fine up until here:

Code:
    mel.character "[mc.name], do you like me?"
    menu:
        "Yes":
            $ mel.affectionUp(1)
            mel.character "Thanks!"

        "No":
            $ mel.affectionDown(1)
            mel.character "Ouch"
RenPy tosses me this:

Code:
I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/story/intro.rpy", line 28, in script
    $ mel.affectionUp(1)
  File "game/story/intro.rpy", line 28, in <module>
    $ mel.affectionUp(1)
AttributeError: 'Actor' object has no attribute 'affectionUp'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "game/story/intro.rpy", line 28, in script
    $ mel.affectionUp(1)
  File "C:\Program Files\RenPy\renpy\ast.py", line 823, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\Program Files\RenPy\renpy\python.py", line 1178, in py_exec_bytecode
    exec(bytecode, globals, locals)
  File "game/story/intro.rpy", line 28, in <module>
    $ mel.affectionUp(1)
AttributeError: 'Actor' object has no attribute 'affectionUp'

Windows-10-10.0.22631 AMD64
Ren'Py 8.2.3.24061702
Anyone have any insight into what could be causing this? I have checked my indentation but I am using all spaces and indented as needed I think.
 

webmasterjunkie

Newbie
Game Developer
Jul 23, 2023
48
153
Disregard. Seems VS Code was creating a running cache of my files and RenPy wasn't getting my latest version. Mod can delete post if they like!
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,420
4,273
"define" keyword is for constants that will not change. So when you did "define mel = Actor(...)" it's not going to do what you intend because later when you save games the changes to the object properties won't be persisted, which can lead to some confusing bugs.

tl;dr: Should use "default mel = Actor(...)"
 
  • Like
Reactions: anne O'nymous

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,420
4,273
Disregard. Seems VS Code was creating a running cache of my files and RenPy wasn't getting my latest version. Mod can delete post if they like!
I'm not sure it would be vscode "caching" stuff, unles you are using it in a way I don't understand (or not saving edited files?)

A running Renpy game does not pickup changes in the game script files at runtime unless you have enabled "Auto reload" mode by:
a) enabling developer mode (which is on by default if you launch the game from the Renpy SDK app)
b) and hit "shift-R" in the game. You'll see the window title change to "<game title> - autoreload"
 
  • Like
Reactions: webmasterjunkie

gojira667

Member
Sep 9, 2019
287
273
I like to keep the Character a define: .
Python:
define character.mel = Character("Melissa", who_color = "#213fe9")

default mel = Actor(
    "Melissa",
    "Neighbor"
    )

    menu:
        "Yes":
            $ mel.affectionUp(1)
            mel "Thanks!"
 

webmasterjunkie

Newbie
Game Developer
Jul 23, 2023
48
153
"define" keyword is for constants that will not change. So when you did "define mel = Actor(...)" it's not going to do what you intend because later when you save games the changes to the object properties won't be persisted, which can lead to some confusing bugs.

tl;dr: Should use "default mel = Actor(...)"
Ah. This is good to know. Just seemed like the logical route to go to have it all under one object. Appreciate you saving me headache later down the road!
 
  • Like
Reactions: osanaiko

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,599
2,240
Python:
init python:
    class Actor:
        def __init__(self, character, name, relationship, affection = 0):
            self.character = character
            self.name = name
            self.relationship = relationship
Code:
I'm sorry, but an uncaught exception occurred.
[...]
AttributeError: 'Actor' object has no attribute 'affectionUp'

Okay. It sounds like you have already corrected it without noticing the original error, but the code you've posted here has an affection parameter passed as part of the Actor object creation. But what it doesn't have is an affection attribute.

I think what you wanted was:

Python:
init python:
    class Actor:
        def __init__(self, character, name, relationship, affection = 0):
            self.character = character
            self.name = name
            self.relationship = relationship
            self.affection = affection              # <--- note this extra line

... and... as others have pointed out, you definitely want to use default when creating your Actor() objects, since (at the very least) the affection attribute is expected to change while the game is running.

And to avoid any confusion, the Character() objects use define because their values never change while the game is running. Even those games that change the MC's name only change the name variable, not the Character() attributes themselves.

For example...

Python:
default persistent.mc_stdname = "Brian"
default mc_name = "Unknown"

define mc = Character("[mc_name"])

label start:

    scene black with fade

    "Welcome to [config.name]."

    $ mc_name = renpy.input("What is your name? {i}(Press ENTER for [persistent.mc_stdname]){/i}", exclude="[]{}")
    $ mc_name = mc_name or persistent.mc_stdname
    $ persistent.mc_stdname = mc_name

    mc "Hi, my name is [mc]."

    return

When the player picks a new name, the value of mc_name changes, but the mc.name attribute of the Character() object is still "[mc_name]". A little confusing since one is mc_name and one is mc.name, but it's the sort of thing you don't even notice once you understand why.

Basic rule of thumb... If a variable will change while the game is being played, use default, otherwise use define.
define statements can still be changed anytime... by the programmer, not due to the actions of the player.
 
  • Like
Reactions: anne O'nymous

peterppp

Active Member
Mar 5, 2020
688
1,175
having the same name declared in two places in bad practice though. you already have the name in the character. no need to add it as a separate parameter to the actor
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,420
4,273
having the same name declared in two places in bad practice though. you already have the name in the character. no need to add it as a separate parameter to the actor
True, but you might have the actor.name be an "internal name" to be used for something like dynamic image file references, while the Character name is for display purposes. "johnotoole" vs "John O'Toole".
 
  • Like
Reactions: Turning Tricks

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
Noob coder raising hand ...

Isn't there also an issue where the OP is using the Character Object to define the Actor Object? I mean, the Character is specifically made in Ren'py to define multiple characters and control their dialogue appearance. It's one the the in Ren'py, after all.

Instead of using ...

Python:
### Actor Object
init python:
    class Actor:
        def __init__(self, character, name, relationship, affection = 0):
            self.character = character
            self.name = name
            self.relationship = relationship
... wouldn't it be better to use another intermediate variable like this... ???

Python:
default char = "mel"

### Actor Object
init python:
    class Actor:
        def __init__(self, char, name, relationship, affection = 0):
            self.char = char
            self.name = name
            self.relationship = relationship
The way I see it, in the OP, it all depends on the order that Ren'py loads the variables. So if the define block is in a file further down the list then the init python block, you are attempting to define an Actor Object using a Character Object that hasn't been defined yet and won't have been "remembered" by Ren'py, as osanaiko already mentioned.

Another thing I see is it's kind of redundant and circular to go to the effort of defining a character object but then having to type mel.character for each line of dialogue. The whole purpose of Ren'py's Character Object is so you can define shortened names and other aspects of a character's dialogue and just use a single letter, if you want...

Python:
default e_name = "Eileen"

define e = Character(name="[e_name]", ... blah blah)

e "Hi, my name is [e_name]."
This is the reason I stay away from pure python scripting - because I have no experience or history with it. Ren'py has created statements and functions to do python-like stuff using it's own simplified commands and it always makes my head hurt when I see people mixing and matching things in the two languages (well, one language and one ... dunno what you call it? container or shell? of Python). Not to mention that when you inject python coding into a Ren'py project, it can cause strange things with Roll Back and other features Ren'py has been made for.
 

webmasterjunkie

Newbie
Game Developer
Jul 23, 2023
48
153
All this is quite interesting. I decided to go this route:
actor.rpy
Code:
### Actor Object
init python:
    class Actor:
        def __init__(self, name, relationship, affection = 0):
            self.name = name
            self.relationship = relationship
            self.affection = affection

        def affectionUp(self, amount):
            self.affection += amount
            renpy.notify(self.name + " liked that!")

        def affectionDown(self, amount):
            self.affection -= amount
            renpy.notify(self.name + " will remember that!")
characters.rpy
Code:
### Characters in the Game
define character.narrator = Character(what_italic = True)
define character.mc = Character("[mc.name]", who_color = "#c8ffc8")
define character.melissa = Character("[melissa.name]", who_color = "#213fe9")

default mc = Actor("Jason", "Player", 100)
default melissa = Actor("Melissa", "Neighbor", 0)
Then my testing on script.rpy shows that the data is being called as a Sayer as needed, and variable values are being saved and loaded on loads:

Code:
label intro:
    call set_custom_actor_relationships

    python:
        mc_name = renpy.input("What is your first name?")
        mc.name = mc_name.strip() or mc.name

    mc "You've created a new Ren'Py game to play where you are the [mc.relationship]."
    mc "Once you add a story, pictures, and music, you can release it to the world!"

    melissa "[mc.name], do you like me?"
    menu:
        "Yes":
            $ melissa.affectionUp(1)
            melissa "Thanks!"

        "No":
            $ melissa.affectionDown(1)
            melissa "Ouch"

    mc "Bye"

    # This ends the game.

    return
This works a treat, so I think I have made some progress. I just want to be able to make a somewhat choice driven VN (not purely kinetic) as my first game so these lessons are great. I appreciate the support as I work through things. As a reward, here is a teaser of my background. Say hello to April, Melissa, and Sarah (respectively):

main_menu.png

I am still working a lot of things out as I go, so no plans on release yet.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,599
2,240
Python:
define character.narrator = Character(what_italic = True)
define character.mc = Character("[mc.name]", who_color = "#c8ffc8")
define character.melissa = Character("[melissa.name]", who_color = "#213fe9")

default mc = Actor("Jason", "Player", 100)
Some random thoughts.

All of these are opinions, albeit based on a few years of aiding other people write their own games.
Nothing you are doing is wrong, but by rethinking a few things in these early stages you might make things easier on yourself and your players.


Short character identifiers

Your Character() variables are the ones that "speak" within game.
Unless you really like typing, you most likely want these identifiers to be as short as possible. Most developers use one or two characters. They often go so far as to make sure all the character names start with a different letter of the alphabet, so they aren't creating character definitions for "Mary", "Madison", "Mel", "Micah" and "Molly". As you work on your game, it will become second nature to think of "Melissa" as "m", "April" as "a" and "Sarah" as "s".

Compare how much you might end up typing:

Python:
character.mc "Hello girls."
character.melissa "Hello [mc.name]."
character.sarah "Hi [mc.name]."
character.april "Hi [mc.name]."

# this would be much less typing (reminder: there are going to literal thousands of lines like this):

mc "Hello girls."
m "Hello [mc]."
s "Hello [mc]."
a "Hello [mc]."

And yes, I've used [mc] rather the more accurate/correct [mc.name]. Due to a function of the Character() definition, it works just fine. Where the character object is used as a string, the .name string is returned instead of the whole object.

You are going to be typing dialogue a lot more than affection = affection + 1 type code, so use the longer names for the Actor() variables and short names for the Character().


The Narrator.

RenPy already has a special Character() called "narrator". Any time you use dialogue within your game without specifying a specific character to speak, it uses the narrator.

You can override its defaults by creating a narrator of your own.

For example:

Python:
define narrator = Character(None, what_color="#FFFF66")

label start:

    "This text is spoken by the narrator."
    return

In my example, my expectation is that normal dialogue (spoken by the characters) will be white and the narrator text will be light yellow.
The narrator tends to get used for actions rather than dialogue "You open the curtain.", "A car pulls up outside the building." or "[a] winks at you."

I find this makes it much easier to tell the difference between a character speaking and the game/MC doing narration.

Which brings me to my third point...


Italic text, internal thoughts, whispers and double quotes.

You've used italics for your narrator. Whilst there's nothing technically wrong with that, italics tends to be used for the MC's internal monologue/thoughts within other games. Some use parentheses instead. Some use both. My personal preference is both. Some games might use italics for whispering too.

This is where having TWO (or more) Character() definitions for the same character becomes useful. Mainly just the MC, since games tend not to show the internal thoughts of "other" characters (the LI's and other NPCs). If you're going to show the thoughts of other characters, you can do this for them too.

Rather than give two examples, I'll merge another suggestion in at this point too. A nice touch is to have the words spoken by a character shown with double quotes around them. It's so easy to do and makes it much easier to tell spoken and other dialogue apart.
The character definition a little longer, but it's something you only need to do once for each character that speaks. Compared with the overall script, it's a tiny amount of extra typing.

Python:
define narrator = Character(None, what_color="#FFFF66")

define mc = Character("[player.name]", color = "#c8ffc8", what_prefix="\"", what_suffix="\"")
define mc_t = Character("[player.name]", color = "#c8ffc8", what_prefix="(", what_suffix=")", what_italics=True, what_color="#CCCCCC")

define m = Character("[melissa.name]", color = "#213fe9", what_prefix="\"", what_suffix="\"")
define a = Character("[april.name]", color = "#213fe9", what_prefix="\"", what_suffix="\"")
define s = Character("[sarah.name]", color = "#213fe9", what_prefix="\"", what_suffix="\"")

default player = Actor("Jason", "player", 100)
default melissa = Actor("Melissa", "neighbour", 0)
default april = Actor("April", "neighbour", 0)
default sarah = Actor("Sarah", "neighbour", 0)

In this example, mc_t is a second MC character that prints (Did she really just do that?), instead of "Did she really just do that?". The _t my shorthand for "thinking" or "thoughts".
I use italics and ( ). I also alter the spoken text color to be a slight gray color instead of pure white. Again, it's a tiny change, but offers a little more differentiation from normal spoke text.
If I also used whispering a lot within my game, I might add a mc_w character or a_w for April whispering.

Python:
define narrator = Character(None, what_color="#FFFF66")

define mc = Character("[player.name]", color = "#c8ffc8", what_prefix="\"", what_suffix="\"")
define mc_t = Character("[player.name]", color = "#c8ffc8", what_prefix="(", what_suffix=")", what_italics=True, what_color="#CCCCCC")
define mc_w = Character("[player.name]", color = "#c8ffc8", what_prefix="\"", what_suffix="\"", what_italics=True,  what_size=16, what_color="#FFDDBB")

define a = Character("[april.name]", color = "#213fe9", what_prefix="\"", what_suffix="\"")
define a_w = Character("[april.name]", color = "#213fe9", what_prefix="\"", what_suffix="\"",  what_italics=True, what_size=16, what_color="#FFDDBB")

I've also changed your relationship values from "Neighbour" to "neighbour". Within RenPy and programming in general, unless you NEED to capitalize something... don't. RenPy specifically prefers lowercase for most things. Don't overthink it, just accept it is the way it is. Trust me, you'll thank me once you realize that RenPy auto-imports all your image definitions as lowercase - which makes much more sense if your image filenames are already lowercase.

In this specific case, I'm going to guess you're going to have line of dialogue like:
mc "Oh yeah, [a]. She's my [april.relationship]."

Invariably, when spoken like that, the word is going to be lowercase. The exceptions are when your MC is directly speaking to someone and uses their title instead of a name. "She's my mom" (title). As opposed to "Oh hi, Mom." (name replacement).

Where you need to change the case of something, use the .
[april.relationship] = "neighbour".
[april.relationship!c] = "Neighbour".
[april.relationship!u] = "NEIGHBOUR".


As I say, this is all just advice. Use the bits that make sense to you and ignore the bits that you disagree with or don't understand.
 
Last edited:

Turning Tricks

Rendering Fantasies
Game Developer
Apr 9, 2022
1,168
2,282
...

Compare how much you might end up typing:

Python:
character.mc "Hello girls."
character.melissa "Hello [mc.name]."
character.sarah "Hi [mc.name]."
character.april "Hi [mc.name]."

# this would be much less typing (reminder: there are going to literal thousands of lines like this):

mc "Hello girls."
m "Hello [mc]."
s "Hello [mc]."
a "Hello [mc]."
Just to highlight this...

Running a LINT on one of my VN's, which is only about 25-30% done it's development so far, returns 7,393 dialogue blocks, containing 87,928 words.

That's a LOT of extra typing!

It's also making me realize why some devs are using Stream Decks to program frequently used macros and commands.... hmmm, damn! I might have to look into that myself!
 

osanaiko

Engaged Member
Modder
Jul 4, 2017
2,420
4,273
I definitely applaud and agree with the idea of using variable names for commonly used things as short as possible.

But regarding techniques for typing reduction - a separate physical hotkey pad that requires taking your hands off the keyboard is not as good as plain old keyboard macros.

Quite a few editors have macro or template functionality built in. Otherwise is a very useful tool for setting up program agnostic hotkeys.

Having said all that, in my experience the time spent typing renpy commands pales in comparison to that spent creating and organising images, or building out code frameworks, or the creative writing itself. But then again I've not actual released a game of my own.
 

webmasterjunkie

Newbie
Game Developer
Jul 23, 2023
48
153
Having said all that, in my experience the time spent typing renpy commands pales in comparison to that spent creating and organising images, or building out code frameworks, or the creative writing itself. But then again I've not actual released a game of my own.
Yeah. Getting into that bit now. Just seeing where I stand on all pieces before committing myself to a long development cycle. Not sure my current PC can handle decent rendering. I don't want to devote my time to creating a (hopefully) compelling short choice drive VN with crappy images. lol
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,586
15,597
In addition to what have already be said, I'll remind this small trick that works every time.

Use full uppercase for the Character and full lowercase for the stats:
Python:
define MC = Character( "[mc_name]" )
default mc = Actor( [...] )

[...]

    menu:
        MC "Should I greet her ?"

        "No":
            mc.isADick += 1
        "Yes":
            mc.isADick -= 1
1) The visual identification is relatively strong.
2) Sayers (Character objects) being really particular, the uppercase mark this.
3) Any mix in the case (because we all stop to press Shift to early time to time) will trigger an error easy to correct.
4) Minus the case difference it's the same name, therefore it's easy to remember.