Ren'Py Needing help with NPCs and Character Selections(Class)

MannyTearson92

Member
Game Developer
Mar 15, 2021
196
621
Hello! Long time, no see.

I'm trying to figure out what Class really is in Python. I figured, doing things like this, will be a lot easier. Any help is highly appreciative. (Seriously!!)

What I'm trying to do is create a bunch of NPCs, but actually, they are Survivors of my game and it's needed to "Play" them. The gameplay is much like Rebuild 2(Flash game, can play it on NewGrounds) and I plan to add a battle system for it, too, so I'm not copying off of others.

I did get close to the gameplay but... the codes are really amateurish looking(I mean, I'm literally pass 7000 lines of codes...). It's basically a copy and paste crap. I created a bunch of layeredimage and... yeah. So far, I got up to 100 characters. But if I can make that less code and more characters... That would be great. I'm trying to make things infinite, in a way. I'm just hoping someone can teach me a bit about what I'm looking for and how to use it.

Example would be, I guess... to create it with Variables within Class. That's it for NPC.


For the Character Selection, it's basically connected to the NPC. The more the players recruit, the more that gets added to the list of Characters. How do I solve this?

Sorry for the long talk. I've honestly been working on it for the past month and a half. Only a week ago that I started looking into this issue. If you don't want to help me out for free, the very least I can do is draw as... I'm broke as the next person. :LOL:

screenshot0005.png screenshot0063.png
 

MidnightArrow

Member
Aug 22, 2021
499
433
Like most things in Python, a class is a poorly implemented, half-assed copy of something that works great in other languages.

You define a class with Python's weird, asinine syntax like this:

Code:
init python:
    class NonPlayerCharacter:
        def __init__(self, name):
            self.name = name
       
        def get_name(self):
            return self.name

    megan = NonPlayerCharacter("Megan")
    chris = NonPlayerCharacter("Christina")
And you call it like this:

Code:
label introduction(npc):
    "My name is " + npc.get_name() + "."
    return

label start:
    $ current_npc = megan
    call introduction(current_npc)
    $ current_npc = chris
    call introduction(current_npc)
    return
Then you pass in whatever other bits of data you want into the constructor. Assign them to self.[whatever] and use functions to fetch the data. So just store hair color, eye color, etc. and then fetch them with your drawing code. You can either store a reference to the image data directly or use an integer to match them (though fucking Python doesn't have consts, enums, or switches, so it's more tedious than it needs to be).

The onus is on you to make sure the art assets match when layered.
 
Last edited:

MannyTearson92

Member
Game Developer
Mar 15, 2021
196
621
(though fucking Python doesn't have consts, integers, or switches, so it's more tedious than it needs to be)
I know what you mean there! :ROFLMAO:

You define a class with Python's weird, asinine syntax like this:

Code:
init python:
    class NonPlayerCharacter:
        def __init__(self, name):
            self.name = name
       
        def get_name(self):
            return self.name

    megan = NonPlayerCharacter("Megan")
    chris = NonPlayerCharacter("Christina")
Alright. How do I randomized names? I had to do something like this.


You don't have permission to view the spoiler content. Log in or register now.

There's 1000 names... Is it possible to add it within the class or should I just stick with this so when players save the game, it'd also save the Variables?


And thanks for telling me this! I've been looking and for some reason, I kept getting sent to unrelated shit like Unreal and Unity and... yeah. Not my proudest moment but I think Google is broken.
 

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,591
2,238
A class is just a template to create an object.
Each object could be considered a variable.

But taking a step back, start simple...

You start with a of character names.

Python:
default char_list = ["Angel", "Barbie", "Cherry", "Debbie", "Elaine"]

label start:

    "*** START ***"

    python:
        for x in char_list:
            renpy.say("", "Say hello to [x].")

    "*** THE END ***"
    return

Now that's an unfair example, because normally you wouldn't code loops within your script, so you wouldn't need to use . Something like that would usually be part of a . But it's a quick and dirty example.

char_list is just a list of strings... in this case, character names.

You could code $ char_list.append("Faith") to add a new name to the list.
At the of the day, this list of strings is just an array. Where char_list[0] is "Angel" and char_list[3] is "Debbie".

But now, instead of a list of strings - imagine a list of something else... a list of a class.

Python:
init python:

    class Person():
        def __init__ (self, name="", age=18):
            self.name = name
            self.age = age

default char_list = [   Person("Angel"),
                        Person("Barbie", 19),
                        Person("Cherry", 21),
                        Person("Debbie"),
                        Person("Elaine"),
                        Person("Faith", 20),
                    ]

label start:

    "*** START ***"

    python:
        for x in char_list:
            renpy.say("", "Say hello to [x.name] who is [x.age] years of age.")

    "*** THE END ***"
    return

How complicated your want to get after that... is entirely up to you.

Edit: For an alternative introduction... check out the links in my .sig
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,561
15,520
But if I can make that less code and more characters... That would be great. I'm trying to make things infinite, in a way. I'm just hoping someone can teach me a bit about what I'm looking for and how to use it.
There's so much covered behind those words, from Ren'Py code to layered images, passing by the character classes and screens. Therefore, what do you want to know exactly, and/or what do you want to effectively do with it.

Basically speaking, what you want to do is not difficult to do with Ren'Py, thanks to its dynamism.
It need a bit of declarations, but by planing it correctly you can perfectly have a single layered image that would cover all the girls. Something that would looks more or less like this:
Python:
# The different images possibly used to build the girl 1 sprite
image girl1_body = "images/[girlName1]/body.png"
image girl1_shirt1 = "images/[girlName1]/dress1.png"
image girl1_bra1 = "images/[girlName1]/bra1.png"

# The different images possibly used to build the girl 2 sprite
image girl2_body = "images/[girlName2]/body.png"
image girl2_shirt1 = "images/[girlName2]/dress1.png"
image girl2_bra1 = "images/[girlName2]/bra1.png"

# The layered image representing the girl 1 sprite
layeredimage girl1:
    always:
        "girl1_body"

    group top:
        attribute shirt1:
            "girl1_shirt1"
        attribute bra1:
            "girl1_bra1"

# The layered image representing the girl 2 sprite
layeredimage girl2:
    always:
        "girl2_body"

    group top:
        attribute shirt1:
            "girl2_shirt1"
        attribute bra1:
            "girl2_bra1"

# Name of the girl currently assigned to the sprite "girl1"
default girl1Name = "Sarah"
# Name of the girl currently assigned to the sprite "girl2"
default girl2Name = "Chris"

label whatever:
    # Show the first girl, wearing a shirt
    show girl1 shirt1
    mc "Hi Sarah"
    # Sow the second girl, shirtless but with her bra on
    show girl2 bra1
    [...]

    Chris "MC, did you know that I'm a shapeshifter ?"
    mc "Really ?"
    Chris "Yes, just name a girl, and I'll looks like her"
    mc "Annie"
    # The sprite "girl2" will now use the images defined for Annie
    $ girl2Name = "Annie"
It's just a raw thinking, there's more and better that can be done.

As for the objects, they should not only host variables representing the girls, but also all the generic code representing their action. This would simplify the code needed to handle them.


Example would be, I guess... to create it with Variables within Class. That's it for NPC.
Then you can starts , or . But it's just a presentation of what Objects are and how to use them. It will not tell you what code you've to put inside them.


For the Character Selection, it's basically connected to the NPC. The more the players recruit, the more that gets added to the list of Characters. How do I solve this?
The answer is in the question, you add them to a list of characters.
Python:
default recruitedGirls = []

default chrisObject = GirlObject( "Chris", [...] )
default gloriaObject = GirlObject( "Gloria", [...] )

label whatever:
   mc "So, you want to goes with me ?"
   Gloria "Yes."
   mc "Welcome then."
   $ recruitedGirls.appened( gloriaObject )
 

MidnightArrow

Member
Aug 22, 2021
499
433
Alright. How do I randomized names? I had to do something like this.

There's 1000 names... Is it possible to add it within the class or should I just stick with this so when players save the game, it'd also save the Variables?
That's pretty much how you do it, but not optimized very well.

If you're creating NPCs with Ren'py language, use the define statement on the list of names. It won't be saved, it'll just be "const" game data, as part of your game files.

You could also define it in a separate text file and read that in during game load. Not sure how much XML or JSON support Ren'py has, but you could look into those.

Edit: TBH if you add the list to the class, I'm not sure if that will be saved as part of the class or if it'll be static (shared by all classes). Other, better languages let you use keywords to define the scope of variables, but Python is like the Jeff Daniels diarrhea scene from Dumb & Dumber. They shit out poorly made designs, and whenever they run into a problem they shit out some half-assed fixes on top of them, and it never ends. Like how there's no keywords and everything is in global scope, except they had to make a "global" keyword in one specific instance to solve some problem with having no scope anywhere else.

OIP.jpg
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,561
15,520
Like how there's no keywords and everything is in global scope, except they had to make a "global" keyword in one specific instance to solve some problem with having no scope anywhere else.
It's not because you don't need to declare them, and that you can address a module scope from any scope who imported it, that variables are all put in the global scope. Python have the same scopes than most script languages (global, object, local and module/library), to which they add protected variables that are "limited" to their initial scope.
 

MidnightArrow

Member
Aug 22, 2021
499
433
It's not because you don't need to declare them, and that you can address a module scope from any scope who imported it, that variables are all put in the global scope. Python have the same scopes than most script languages (global, object, local and module/library), to which they add protected variables that are "limited" to their initial scope.
Well, it'd be nice if Python had explicit keywords, so I could just say "static string[] list_of_names = { "Annie", "Betty", "Carol" };" and know whether they were being stored statically or as class variables. But iirc Python objects can't do that because they're really just dictionaries under the hood, which is why they need all this fucking weird syntax and name mangling and passing self into every function. So they're not even real objects, they're just maps that look like objects.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,561
15,520
Well, it'd be nice if Python had explicit keywords, so I could just say "static string[] list_of_names = { "Annie", "Betty", "Carol" };" and know whether they were being stored statically or as class variables.
Why have a keyword when you don't need it to be explicit ?
  • Everything declared at class level is class related ;
  • Everything used in a method and not prefixed is a local variable ;
  • Everything used in a method and prefixed by "self" (or whatever name you'll give to it) is object related.

Code:
class Whatever():

    classVariable = [ "Annie", "Betty" ]

    def __init__( self, girls ):
        self.objectVariable = girls

   def method( nabuchadnezzar ):
       localVariable = True
       nabuchadnezzar.anotherObjectVariable = nabuchadnezzar.objectVariable

   stillAClassVariable = "Babylon"

But iirc Python objects can't do that because they're really just dictionaries under the hood,
Every single thing in Python, including literal values, is an object.


which is why they need all this fucking weird syntax and name mangling and passing self into every function.
There's nothing weird in Python syntax (or perhaps have you never used Perl, among many other). As for the self argument, it's due to the voluntarily dynamic approach followed by Python ; this in order to be duck typing compliant. At anytime you can extend an object independently to its class, even and especially once the code is running ; and by "extend" I talk about the addition of methods, not variables.


So they're not even real objects, they're just maps that look like objects.
It don't need to go really far to tell that there isn't a single language where objects are something else than mapped data once you look under the hood. The only thing that effectively differ between them is how strongly, or lax, are the control over this mapping.
And it happen that in Python they are relatively lax because we are all grown adults, while scripts languages are made to be dynamics and don't lead to too much headaches. This because the philosophy behind Python is to keep things as simple as possible ; what they achieved to do.
 

MidnightArrow

Member
Aug 22, 2021
499
433
There's nothing weird in Python syntax (or perhaps have you never used Perl, among many other). As for the self argument, it's due to the voluntarily dynamic approach followed by Python ; this in order to be duck typing compliant. At anytime you can extend an object independently to its class, even and especially once the code is running ; and by "extend" I talk about the addition of methods, not variables.
I consider duck typing itself to be a design flaw, so saying "It's duck typing compliant" doesn't really sway me.

Anyway, to get this back on topic...

To the OP, iirc an "object" in Python is just a dictionary with some weird semantics to make it look like a real object in other languages. So

Code:
class MyObject:
    def __init__(self, value):
        self.value = value

    def get_value(self):
        return self.value
is the same as

Code:
def get_value_from_dictionary(dictionary):
    return dictionary["value"]
except you don't get any type safety or encapsulation and you need to deal with weird shit like double underscores to mangle the name of your constructor rather than just use the name of the class like better languages do.

It's just a slightly more strict way of using function calls with a map/dictionary.

This isn't to be confused with the Python saying Ann mentioned, "Everything is an object", which means all variables are objects inside the code running the interpreter. So if you declare an integer, the code running Python creates an integer object. That's not the same as the "object" (a glorified dictionary) you create using the interpreter.

Edit: The code I posted above isn't totally correct. A Python object has a "__dict__" variable inside it that stores its fields and methods.



So you'd need to access that to get the object's properties. But since Python has no safety whatsoever, reaching into an object and doing whatever you want with its internals isn't a problem.
 
Last edited:

MannyTearson92

Member
Game Developer
Mar 15, 2021
196
621
How complicated your want to get after that... is entirely up to you.
Wait, it gets more complicated?! :ROFLMAO: Joke aside, this is really handy. Including your tutorials. Thank you for taking the time to tell me these things.

The answer is in the question, you add them to a list of characters.
Ah... So it was that simple this whole time? I didn't need to beat myself up with a hammer, trying to nail this whole thing? o_O

Thank you for taking the time to teach me this. I really had no idea. Seriously, thanks. :)

Python is like the Jeff Daniels diarrhea scene from Dumb & Dumber.
This had me dropping to the floor! The most joke about Dumb and Dumber I've heard was pretty retarded but this one actually cracks me up. :LOL:

To the OP, iirc an "object" in Python is just a dictionary with some weird semantics to make it look like a real object in other languages. So
Looks to me like it all return to "value". And it looks to me like that top one might be more simple to deal with than dictionary. My friend in the JS side of things often says "If it's already written, no sense in rewriting it unless you're including a whole different blueprint for it."(Or something like that Lol)

Thanks, MidnightArrow, for taking the time to help me.



For everyone that posted... Seriously, guys. You are awesome! I learned a lot more than I did the whole week on searching it. So you guys saved me a lot more time. Thank you. I'll probably have more questions involving this topic after a few days of trials and errors and whatnot. I'm an Artist at heart. So when it comes to watching my art piece come to life through code... It brings a whole new feeling of satisfaction. So I'll say it again... THANK YOU! :D