Ren'Py class based state system for controlling character attributes and so on

User #1751331

Member
Oct 30, 2019
193
158
First off the article is posted here because the "Programming" in "Programming, development &Art" Secondly, the reason I used Renpy above well its probably the most appropriate because it can use pure python blocks in it.

The primary part is the class bSM. I gave single very basic example of it's use. Were I used it to make a simple corruption demo you could however use it for many attributes.
The method is fairly fast and allows you to create rather clean and easy manageable code. Meaning if you need to make changes later on because you don't like something well you can do it rather easily. It should perform rather well speed wise in comparison to using a lot of if statements and should also be a lot easier to follow.

To create an attribute use : attributename = bSM(start value). Start value is whatever value you want that character to start that attribute with.
For most uses you shouldn't need to make changes to the class itself just create new states and save them using save_state().
When you want to test against that state use the function checkF( state_you_want_to_check).
there are functions to increment, decrement,set and get the value of the attribute.
state_count currently really isn't being used.

Hope this comes in use for some one.

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

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

I corrected for the issue with class variable vs Instance variables. Yep, I don't use python that much. Most C and C++.
 
Last edited:
  • Like
Reactions: Enough

Enough

Newbie
Oct 11, 2018
36
44
I am new guy to programming and I have question. What is adventage of using this kind of system instead of the simpliest one, with something like this. Because I am during making my own game and I am using this kind of simple system, but maybe I don't know limitation or other problems with this and should I use more advanced mechanics for any kind of statistic?

Python:
define X = 50
$ X += 5 #example after action which increase number

# choice XYZ

if X > 60: #example usage of points
    $ X -= 10
    jump to label with action which costs 10 points
    $ flag_X_60_done = True
elif X > 40:
    jump to label with free action when at least 40 points
    $ flag_X_40_done = True
else:
    "you don't have enough X points"
    jump to label after action

Also example from visit in shop which already works in my game.

Python:
label city_shop_day1:
    menu:
        "Sim card (5£)":
            if bought_sim_card == False:
                $ bought_sim_card = True
                "(Sim card purchased {b}-5£{/b})"
                $ cash -=5
                jump city_shop_day1
            else:
                "I have already bought sim card"
                jump city_shop_day1

        "Groceries (20£)":
            if bought_groceries_day1 == False:
                $ bought_groceries_day1 = True
                "(Groceries purchased {b}-20£{/b})"
                $ cash -=20
                $ itemek_groceries +=7
                jump city_shop_day1
            else:
                "I have already bought groceries"
                jump city_shop_day1


        "Finished":
            if bought_groceries_day1 == False:
                "I need to buy groceries"
                jump city_shop_day1
            else:
                i "That's all what i needed"
                jump city_explore_back_day1
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
Hope this comes in use for some one.
Well, it's interesting, but you should have also added an effectively working "How to use it with Ren'py". Because thrown like this, it will only be useful for people who will understood how to use it. And like the second bug is insidious, many devs will probably not see it.

This way the corruption is saved, but the game is broke :
Python:
init python:
    # put the class and function creation here

default corruption = bSM(10)

label start:
    $ corruption.save_state(10,wearBikini)
    $ corruption.save_state(20,showtits)

    $ corruption.inc( 20 )
    $ a = corruption.getValue()
    "Corruption [a]"
    "PLEASE SAVE, QUIT RENPY, RESTART AND LOAD, HERE"

    # Oops, what's this error screen ?
    if corruption.checkF(showtits):
        "It works"

    $ a = corruption.getValue()
    "Corruption [a]"
This way the game works, but the corruption isn't saved :
Python:
init python:
    # put the class and function creation here

    corruption = bSM(10)
    corruption.save_state(10,wearBikini)
    corruption.save_state(20,showtits)

label start:
    $ corruption.inc( 20 )
    $ a = corruption.getValue()
    "Corruption [a]"
    "PLEASE SAVE, QUIT RENPY, RESTART AND LOAD, HERE"

    # Sweet, it finally works
    if corruption.checkF(wearBikini):
        "It works"

    # Wait, where's the 20 added before ?
    $ a = corruption.getValue()
    "Corruption [a]"

There's a way to make it works, I let you find which one, but it imply a lot of post processing ; too much to fit with :
You can also use it for keeping track of stuff like gamestate and progress. Another way to look at it is a clear way to handle control flow of the game.
The whole class have to be refactored to be really usable.
 

User #1751331

Member
Oct 30, 2019
193
158
I am new guy to programming and I have question. What is adventage of using this kind of system instead of the simpliest one, with something like this. Because I am during making my own game and I am using this kind of simple system, but maybe I don't know limitation or other problems with this and should I use more advanced mechanics for any kind of statistic?
There several advantages. Nested if then else statements get to be very complex over time. This doesn't.
Nested if then else statements slow the game down the more you have the slower it works.
It also provides a clean way to do most the work you need out side of the scenes so they aren't cluttered up.


Well, it's interesting, but you should have also added an effectively working "How to use it with Ren'py". Because thrown like this, it will only be useful for people who will understood how to use it. And like the second bug is insidious, many devs will probably not see it.

This way the corruption is saved, but the game is broke :
I posted this while working on my own project which is in C++. I ported this over. I guess I could go ahead and create a demonstration game using it. But that would mean stopping my current project and work on it. Honestly, I spent more time than I should doing just this and again answering questions.

Also you should look at the second example it's a bit better. The point is to use the class to store and keep track of the variables. It's stored in Corruption.value.

I wish you would hold back on using the term bugged. There is no bugs in the current code.
It works with both py 2 & 3. I fully tested it.

That said I appreciate the examples you gave. Personally I would use multiple files. One for the bSM. One for the player class and Initialization. That way it is clear when stuff is meant to happen and keeps the code separated.

You could also use that type of system for flow control of the game. I do in C++. That said C++ also has switch case which is easier but not modifiable on the fly like this can be. Technically I left out a function because it really isn't used most the time and with the difficult the developers have on here keeping stuff working right well it would just add to the problem. That function is for removing states.

People learning to use renpy should also get used to checking documentation.

I just think if you are going to use a tool learn to use it to the best extent possible.

you can call those functions anywhere in renpy by putting it in a python block.
Python:
python:
     Corruption.getValue()
The point of using this is to eliminate most the work being done in the renpy space and localize it to the functions and test states.
If a test succeeds it returns a 1. Which if you need a trigger in a scene to change something you just need to make a state test and then check it in the scene.
The point is there is no real need to bring the value outside of the class if it is used right.

Lets say I wanted to create a menu bar or have a popup showing the change in corruption of a character I would do that under the character class I showed in the second example.
 
Last edited:
  • Like
Reactions: Enough

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
Honestly, I spent more time than I should doing just this and again answering questions.
*Sigh*


Also you should look at the second example it's a bit better.
Nope, it's worse. Not only it don't solve the flaw of the code related to it's use with Ren'py, but also it break Ren'py itself by overwriting the function that create a character.


I wish you would hold back on using the term bugged. There is no bugs in the current code.
You choose to tag the thread "Ren'py", and the code you gave will never works as it with Ren'py. Either breaking the game, or never saving the values embedded by the class. Therefore it's a bug, and nothing else.


It works with both py 2 & 3. I fully tested it.
Yet your tests haven't shown you that even with pure Python the class is broke ?
You declared stateF and stateC as attributes of the class, and not as attribute of the object, which imply that every object will share the exact same values for those two dicts.
Code:
obj1 = bSM( 10 )
obj2 = bSM( 1 )
obj1.save_state( 10, whateverFnct )
obj2.stateF[10]
<function whateverFunct at 0x...>
Oops...

It's the same for value and state_count, but like they are scalar-like attributes, they are overwrote by the object attributes with the same name.


And it also don't works with Ren'py.
Since it use Pickle to save the data, only attributes stored in the object __dict__ will be saved, which isn't the case of attributes declared as part of the class. Therefore, both stateF and stateC will be reset after a load.
And if you define their value at init time, as work around for this problem, then the objects will never be saved, which make them useless.


People learning to use renpy should also get used to checking documentation.

I just think if you are going to use a tool learn to use it to the best extent possible.
Funny that you wrote this...
 
  • Like
Reactions: Enough

User #1751331

Member
Oct 30, 2019
193
158
*Sigh*

Nope, it's worse. Not only it don't solve the flaw of the code related to it's use with Ren'py, but also it break Ren'py itself by overwriting the function that create a character.

You choose to tag the thread "Ren'py", and the code you gave will never works as it with Ren'py. Either breaking the game, or never saving the values embedded by the class. Therefore it's a bug, and nothing else.

Yet your tests haven't shown you that even with pure Python the class is broke ?
You declared stateF and stateC as attributes of the class, and not as attribute of the object, which imply that every object will share the exact same values for those two dicts.
Code:
obj1 = bSM( 10 )
obj2 = bSM( 1 )
obj1.save_state( 10, whateverFnct )
obj2.stateF[10]
Oops...

It's the same for value and state_count, but like they are scalar-like attributes, they are overwrote by the object attributes with the same name.

And it also don't works with Ren'py.
Since it use Pickle to save the data, only attributes stored in the object __dict__ will be saved, which isn't the case of attributes declared as part of the class. Therefore, both stateF and stateC will be reset after a load.
And if you define their value at init time, as work around for this problem, then the objects will never be saved, which make them useless.

Funny that you wrote this...
Thanks, for point the issue out. But you could have also just said there was an issue between class and instance variables.
Like I said I ported it from C++. We don't have stupid shit like that in C++. The closes thing we have is static variables.
We only do that in a class when you want to create a singleton. Which is the least used and pretty much the worst design pattern. I write about 1 line of python for every 5 to 10K I write in C/C++ even php.
Of course I'm assuming you knew what the issue was and just didn't say and point it out.

It only took a few seconds to correct.

As for breaking renpy character. Just rename the class. That was so hard to rename to myCharacter.


Python:
class bSM:
    def __init__(self,value):
        self.state_count = 0
        self.value = 0
        self.stateF = {}
        self.stateC = {}
        self.value = value
   
    def save_state(self,value,function):
        self.stateF[value]=function
        self.stateC[function]=value
        self.state_count += 1
       
    def checkV(self,value):
        if value<=self.value:
            return self.stateF.get(value)()

    def checkF(self,function):
        if self.stateC[function]<=self.value:
            return self.stateF.get(self.stateC[function])(self)
           
    def inc(self,amount):
        self.value += amount
    def dec(self,amount):
        self.value -= amount
    def getValue(self):
        return self.value
    def setValue(self,value):
        self.value = value
       
       
def wearBikini(w):
    print("Wearing Bikini\n")
    w.inc(10)
    return 1
   
def showtits(w):
    print("showing Tits\n")
    return 1
   
def nude(w):
    print("Nude\n")
    return 1
   
def showAss(w):
    print("showing Ass\n")
    return 1
   
def showPussy(w):
    print("showing Pussy\n")
    return 1
   
Corruption = bSM(20)
test = bSM(20)

Corruption.save_state(10,wearBikini)
Corruption.save_state(20,showtits)
test.save_state(10,nude)
test.save_state(20,showAss)
test.save_state(50,showPussy)

if (Corruption.checkF(showtits)):
    print("Corrupt.Tits worked")
   
if (test.checkF(showAss)):
    print("test.showAss worked")
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,957
16,188
It only took a few seconds to correct.

As for breaking renpy character. Just rename the class. That was so hard to rename to myCharacter.
None of the problems where hard to fix, I even said it in my first comment.

But well, you're the one who gave this piece of code to the community, it seem natural that you also have to be the one who provide it "bug free". Especially if you later claim to have fully tested it, and advice to read the doc.
We are all just humans, we all make errors, including me ; I need to fix the code I give here at least once each two/three weeks. But acknowledging them is precisely what make the difference.
 

User #1751331

Member
Oct 30, 2019
193
158
None of the problems where hard to fix, I even said it in my first comment.

But well, you're the one who gave this piece of code to the community, it seem natural that you also have to be the one who provide it "bug free". Especially if you later claim to have fully tested it, and advice to read the doc.
We are all just humans, we all make errors, including me ; I need to fix the code I give here at least once each two/three weeks. But acknowledging them is precisely what make the difference.
I agree it was my responsibility. I did however test it with the code present. Which did work as tested.
That said I could have tested beyond that and probably would have then caught the error.
Honestly, the entire class instance issue I forgot about. It's one of the dozen reasons I don't use python for anything major.
It's like the developers of the language like going against common convention and intentionally like to build bad issues into the language. You got that, the garbage collection method, GIL ... and a lot more. Way to much to go into for this discussion and way off topic.

The point of the post was to give an example how developer using renpy or python in general could clean up a lot of code and boost their performance.
Out of those two reasons the biggest one should be cleaning code up and managing it.

Given that renpy has a character class of it's own. Instead of having name and so on in the class I made one could modify it and inherit the renpy class.
If developers bother reading the documentation like and
it shouldn't take much to figure out how they can apply this.

I guess however, I can come back in time and update the post. Such as dropping it into a renpy project and or breaking it up into files that can be dropped into a project so people can see how to make use of it.

It would however be far more advantageous for them to simply download it and play with it and see how it works and learn to adapt it. Then they might realize how many other things they can use it for and how easy it is to expand and how it keeps code clean.

At least I didn't paste a pure FSM(finite state machine) here and expect them to figure out how to use it. I at least made a modified state machine that is easy to use and understand and powerful enough to make real use of.