Devlog: Coding an Object in Ren.py RPG


Well... I don't know why I'm suddenly writing this but... time to share some coding stuff. It's not like I'm a professional at coding and stuff... and I'm still actively learning. So this is not like a tutorial or something, I'm just sharing... my thing. You know, Outland Wanderer is an RPG Visual Novel. And well why did I chose this genre... half because "inspiration", and half because I really like the genre, and the notion of being able to roam around and fuck about and choose where your story goes. I found that very immersive. Of course, Traditional Visual Novel is a guided, more concentrated experience that player can immerse themselves in a totally different way. And I really appreciate those that can do it well. Either way, I don't know why I'm rambling again. But you know, RPG system are good~ and renpy is very flexible (albeit it has its limitation...) And I kinda want there to be more rpg games because personally I'm in love with it. Of course it's kinda easier to make in RPG maker but damn I'm a sucker for both story thing and game thing. I'm just gonna gloss over objects very lightly because my rambling cooldown is gonna go down as soon as I got bored.

[ Disclaimer: I'm a dumb dumb at coding, so pardon me if I make any glaring mistakes]

This is about ren.py in python btw. I think you'd have to first understand the basics of renpy, with label and screen functions.

https://www.renpy.org/doc/html/label.html

https://www.renpy.org/doc/html/screens.html

^ Also you can check out the renpy tutorial game when you use the launcher, should give you some basic knowledge of the engine/language.

This sharing thing is mostly intermediate level~ it'd be kinda difficult if you are all new to the code and stuff. So I think I might not explain it too well...

The most integral thing about coding an RPG is the concept of object! I think I really underestimated its value when I first started coding this game. And now, I'm repenting my sin over the OOP(object-oriented programming) overlord. The concept is, you have a thing, you make it an object. That's it. If you have an item in game, you make it an object; a skill, an object; a place, an object; a whateverthefuck, an object. Anything that you can think of, make it an object. But mister fffehsggeahh, how do I make an object...?

Well... you have to make a class, and objects... are instances of a class. For example, you give a place a Place class.


So... how do you do it... (remember to start coding blocks with

init python:

It is initialising python, the line of code in "init python" is executed before other stuff like labels and screens. don't mind the numbers between init and python, they're like executing sequences and stuff. Not very important. (I think)

But first... you'd need the class to work with Rollback , just import stuff for you to do the stuff you know:

init python:
    import renpy.store as store     
    import renpy.exports as renpy      
    
    class Link(store.object): 
        pass

Ok, not as much stuff to explain because I don't know that much about it too, I got it from quest log tutorial from jw2pfd from lemmasoft forum. It helped build my quest system in the game, I didn't end up using it because it got too complicated and I didn't need it to be that way in the game, but I learned a lot from looking at the code tutorial. Anyway, it works with other class when you put this class inside it (kinda like a subclass.)

class Place(Link):              
    def __init__(self, name = None, description = None, item = [], enemy = [], drop = [], discovered = False):                      
        self.name = name                   
        self.description = description                      
        self.discovered = discovered                 
        self.item = item                 
        self.enemy = enemy                  
        self.drop = drop

So here's a class from my actual game. it is a Place class, so it has a name, a description, items and enemies that can be found, and a state of whether it is discovered or not. self is how it calls itself (open recursion?). And these are its attributes, so in this class (Link is the rollback thing we did above, if you don't need it, just leave it as (). ) we first need to define their attributes. The name self is like a must thing, it is seperated by a single comma, where the thing after =, is the default state. For example, by default, a place's name is None, a place's item is []. But it is very rare that it remains the same, because you'd specify when you make an object. after that, just copy the "self.attribute = attribute" shenanigans, technically, the attributes from the def __init__() line is those that you can specify. But under that, you can add an attribute that is true for all of the object in the class. For example, you can add an attribute... like x. so you add a new line... self.x = 0. and for this, every place's x is 0 when it is initialised. And for the other's their attributes will follow the variables above, so self.name = None, self.discovered = False... if you have not specified the object.

default green_forest = Place("Green Forest", "A peaceful forest where berries are often collected by adventurers. {p} However, the grass floor is infested with slime monster where escaping would be the first option.", [redberry_item, blueberry_item, stone_item], [slime], [slimeball_item, slimecrystal_item, slimybone_item], True)

Here's an object in Place class, called green_forest. its name is "Green Forest" and its items are an array of items (objects in Item class...), you can't put it in the init python block so just put it somewhere else. The thing is, this line specifies the attributes of the object green_forest. so how do you access the object's attributes, very easy, you use green_forest.name, green_forest.description etc.


Below are screen function stuff, which is a lot different than the codes above.

 if selected_location != None:          
    vbox:             
        xpos 1635                                      
        yalign 0.035             
        xmaximum 265             
        spacing 20             
        frame:                 
            xpadding 10                 
            ypadding 10                 
            label "[selected_location.name]"  text_color "#301410"             
        label "[selected_location.description]"  text_color "#111111" text_size 25

This is the code of how I displayed the names and description of the places in the game, it is inside a screen function. Don't mind the other screen stuff, the mechanism is that, there's a lot of buttons, each hovered buttons shows their name and description, and to do that, just use SetVariable function and set selected_location to the specific place.

imagebutton:         
    xalign 0.665         
    yalign 0.44         
    idle "mappoint"         
    hover "mappointhover"         
    hovered SetVariable("selected_location", green_forest)         
    action Hide("map_screen"), Jump("main_green_forest")

 This is the button for green forest, when you set the selected_location to green_forest. The labels above will show green_forest.name which is green forest's name ("Green Forest"). And this is like the basic/integral stuff about objects and stuff.


Here's like an advanced use of Class System (I learned them from elaine's renpy tutorial on youtube btw, it's like the same thing but I made some tweak to better fit the game. check her out for a detailed explanation!):


Also, itch messes up the indentation, indentation is very important to the python language so try to view it properly either copy on clipboard or stuff if you want to actually look at it.

class Equipable(InventoryItem):
        def __init__(self,name,img,value,description,number,stat=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]):
            InventoryItem.__init__(self,name,img,value,description,number)  #get attributes from InventoryItem Class
            self.stat = stat
            self.in_equipment = False    # Check if it's in the equipment section
            self.equipped_to = False     # Where it is equipped to, usually either player or no
        def equip(self, target):
            self.in_equipment = True     # It is in the equipment section
            self.equipped_to = target    # It is equipped to "target"
        def unequip(self):
            self.in_equipment = False    # It is no longer equipped
            self.equipped_to = None

Here's a code for equipment. It is a subclass, from the item class. so the first line is to get the same attributes from the item class. There are two functions accompanied to the class. equip and unequip, 

class Weapon(Equipable):
        def __init__(self,name, img, value, description, number, stat, wpn_type):
            Equipable.__init__(self,name,img,value,description,number,stat) #get attributes from equipable class
            self.wpn_type = wpn_type              # weapon type
        def equip(self,target):
            if target.weapon != None:             # Unequip current weapon if there's a weapon. 
                target.weapon.unequip()
            Equipable.equip(self, target)         # Use the function from Equipable class
            target.equip_weapon(self)             # Player equips the selected weapon 
            inventory.remove(self)                # Remove selected weapon from inventory
        def unequip(self):
            self.equipped_to.unequip_weapon()     # self.equipped_to = player, Player unequips the selected weapon
            inventory.append(self)                # Return selected weapon to the inventory
            Equipable.unequip(self)               # Use the function from Equipable class

This is the juicy part. Basically you'd want to use these functions to equip a weapon. For example, if you are equipped with a woodensword_item, and you want to equip an ironsword_item, you'd use the function ironsword_item.equip(player), which calls the function in line 5. This function will first unequip your weapon, so, as target.weapon = woodensword_item, it unequips the woodensword_item with the function in line 12, it then updates your weapon's variable with the equipment above. (You can honestly clean it up if you think only the player can equip item), the next line makes sure to make a copy of the ironsword_item and put it on the player (see the code below), and then the last line removes the original copy of the weapon from the inventory.

def equip_weapon(self, weapon):
            if self.weapon != None:               # Unequip current weapon if there's a weapon.
                self.unequip_weapon()
            self.weapon = weapon                  # Player equips selected weapon
            self.statAdd(weapon)                  # adds the weapon's stat to the Player
        def unequip_weapon(self):
            if self.weapon != None:               # You cannot unequip if there's no weapon equipped
                self.statRemove(self.weapon)      # removes the weapon's stat from the Player
                self.weapon = None                # Player's weapon is now empty

Above is the code from the Player Class. The class is too long so I didn't crop the whole thing. But this explains "target.equip_weapon(self)".



It is very integral to the RPG system and any system actually, it's a good habit to make a class for everything so it'd be much more easier when you build up more stuff upon it. Here's some system in my game that uses objects (I can only speak from my game's perspective ;v).

Inventory System: The biggest, most complicated ones, basically it is linked to a lot of other systems like dungeons, shops, and places like the example above. And I had a lot of bad memories with them, but the basic gist is, make a subclass for each type of items (have a lot to do with inheritance and stuff) for example, equipment, consumables, materials, they all uses different functions e.g. equipping, consuming, and then make some functions, for quest requirement, add a function that checks item's number.  Also, you'd need different objects for the same items, (For example, shop's berries should have a different number of items than the berries in your inventory. If you treat it as the same object, their number cannot be seperated.) you'd need to learn to append new objects and stuff. but I'm just glossing over because it's a huge huge huge topic...

Quest System: Well kinda straight forward, basically the most important is the status thing, different value of the quest status can mean it's either finished, or not even discovered. You can change them in labels as long as you start the line with $. 

Character Stat: Well, you just need a lot of stats that changes over time, hp, mp, and a lot of stuff~ it can also be applied to monsters.

And other stuff like, recipe system, effects and skills, anything that you wanna add to the game~


This thing is just a very very very very shallow rambling about the class and object system~ there's a lot of other usage of the system that I think requires more experiments and calculations. I can talk about it like, more, further later on, maybe discuss the juicy stuff of the code if anyone actually wanna hear haha. Else maybe I'll ramble about story next time.

(this is so long and boring lmao)

Get Outland Wanderer

Download NowName your own price

Comments

Log in with itch.io to leave a comment.

(+1)

Just so you know, with inheritance the subclass has the same functions as the base class. Which is to say, you don't need to call (from the weapon class):

Equipable.equip(self, target)

You can actually just call:

self.equip(target)

Calling it by class is normally only done if you are using multiple inheritance and accidentally gave two of the base classes conflicting functions with the same name, or you are for some reason calling it from outside the class and its subclasses.

By the way, when self is passed as an argument to a function it isn't actually calling itself, it is just passing itself as reference. Remember how I mentioned you could call a class function from outside a class? This is what makes that possible. Looking back at the above function, you might notice that there are two arguments in the first but only one in the second. When calling from outside, you need to provide the object, but by calling from the object (in this case 'self'), it implicitly passes itself as the first argument. So technically, both have the same two arguments.

That's why self is a must thing, as the object itself will always be the first argument when called normally. If you really want, there are ways to pass the object reference in other positions, but those are outside the scope of an itch.io comment and have no benefit other than novelty (and some downright masochistic downsides).

By the way, self is just a variable name. You could alter:

InventoryItem.__init__(self,name,...):

to:

InventoryItem.__init__(litterallyAnything,name,...)

but please never do that. It really hurts code readability, and you would also have to change

self.name = name

to

litterallyAnything.name = name

and etc.

(+1)

Let me add one caveat, when calling the __init__ function of the super class from the sub class, you should use the SuperClass.Function(self, args) format. I'm not super familiar with Python 2, but I think the reason is that at this point the object is not yet fully initialized so you can't call from it yet. I think there is a function called super() that gives access to the base class so you can call it without passing self, something like

# The following would be the first line of weapon class __init__
super().__init__(name,img,value,description,number,stat)

But you need the base class to inherit from object and the one time I tried coding with Python 2 I couldn't get it to work (I was using an unofficial interpreter, so you may get better results).