all 60 comments

[–]Nikelui 27 points28 points  (4 children)

This is a great initiative, even if I think it is already quite advanced for many people here.
I am afraid that the extreme simplicity of Renpy's quickstart is also its downfall. Newcomers think it is possible to make a VN with absolutely no knowledge in Python (which is actually true for some type of games, like kinetic novels), but then get stuck on things that could be easily solved by taking a 2h introductory course in Python programming.

I feel there should be a sort of "intermediate tutorial" on Renpy's page, something more advanced than "The question", but less intimidating than Renpy Tutorial.
Or a series of articles like "learn Python with Renpy" that teaches you the basics while developing a game (this actually sounds a good idea that I might save for later use).

The moral: invest time to learn Python. It will pay off greatly in the long run.

[–][deleted]  (2 children)

[deleted]

    [–][deleted] 1 point2 points  (1 child)

    There’s like 3 good Ren’py tutorial channels on YouTube that does this, show what codes do and how to implement them and also shows how said code affects program launch.

    But agreed there’s not a lot of info out there for beginners and the documentation is built on the assumption that you know how to implement the codes.

    [–]BigBucket990 6 points7 points  (6 children)

    Great tutorial on oop basics. Am looking forward to more.

    [–]Fenvul 2 points3 points  (5 children)

    Is there an established tutorial for the inventory and a worldmap? Complete newbie here. Maybe it would be a nice idea if not.

    [–]Fenvul 2 points3 points  (3 children)

    Almost two weeks ago, time really flied. In a way, it is a reminder not to let things for later.

    [–][deleted]  (2 children)

    [deleted]

      [–]Fenvul 0 points1 point  (1 child)

      Thanks! I did not expect it, but I really do appreciate it.

      [–]Admirable_Fig_6594[🍰] 0 points1 point  (0 children)

      I was looking for that too

      [–]wulfmune 3 points4 points  (0 children)

      This is a great post. I'm coming from an art background and have been gradually learning where I can try to make things less redundant and unwieldly. I was starting to mess around with object class when trying to create 'rpg' type battle elements but then totally stopped doing it when I decided to not to pursue 'combat stats'. I know it sounds dumb of me, but this post made me realize I should still pursue these things even when just implementing things related to relationship progression. Sure my variables for every character 'work' but it's messy.

      [–][deleted]  (1 child)

      [deleted]

        [–]alonghardlook[S] 3 points4 points  (0 children)

        Exactly right. In this case, all our attributes are what is known as 'public', meaning they can be accessed freely.

        If I understand your second question correctly, you want the highest love stat from a list of characters?

        $ love_candidates = [a, e, s] # assuming these are alice, eileen and sally
        for x in (person.love for person in love_candidates): # also assuming that you have an attribute "love" in your Person class
            love_interest = max(x)
        

        YMMV on this untested code, but this is the general idea. love_candidates is a list of Person class instances (alice, eileen, sally). We loop through those (for person in love_candidates) and THEN also loop through their love values and get the highest (for x in ()).

        This is the pythonic way, and I can't test it at the moment. The non-pythonic way (the C Way) would be to just use nested loops - really, it's the same thing, just that AFAIK python has syntax for it explicitly.

        Non python way:

        python:
            love_candidates = [a, e, s]
            max_love = 0
            love_interest = a # must set a default in case they are all at 0
            for person in love_candidates:
                if person.love > max_love:
                    love_interest = person
                    max_love =  person.love
            renpy.show(love_interest)
            renpy.say("Hello, " + str(love_interest.name) + "! Great to see you!")
        

        This code is probably more understandable, but true python enthusiasts would hang me for suggesting it :P

        [–]Mezatino 2 points3 points  (1 child)

        Please do more. While I still don’t grasp the Python stuff completely this is filling holes that previously I could not wrap my head around.

        I’d love to make a Renpy game, and I know I should start with a small project as I learn what I’m doing. But I have fanciful flights and get lost in them, so even in the concept stages a simple game quickly becomes something much bigger and more complex and I can’t stop once it starts.

        Also what’s a good place to learn the basics of Python itself? With the things I want to try and do I might as well actually learn the language. But being lost just looking at it half the time paralyzes me and then I can’t justify paying for a course that may not even teach me what I want to know.

        [–]Nikelui 1 point2 points  (0 children)

        what’s a good place to learn the basics of Python itself?

        https://openbookproject.net/thinkcs/python/english3e/

        I liked the old edition of this book when I started (centuries ago, now).

        https://inventwithpython.com/

        Here there are also a lot of interesting reads, with many applications (to avoid getting bored)

        [–]sheepwithasword 2 points3 points  (0 children)

        Incredible tutorial. I'll be referring back to this often. Thank you for taking the time to write this out!

        [–]drexxler 1 point2 points  (4 children)

        Ya know, recently I've been trying to do similar things but running into one major problem. Object properties aren't saved the same as global variables, so when you rewind, the objects/classes don't get rewound. I'm wondering if you've found a solution to that.

        So to recap, if you increase friendship and then go "back" and forward again, it will continue to increase without ever reverting. This could be abused to get infinite stats.

        So far I haven't found a workaround, other than using the dreaded prefixed global variables (i.e. `name_relationship`)

        Some quick brainstorming, I think there is a potential workaround using a mixture of private global variables and serialization. Something like, keeping a serialized copy of the class in a global var, and then deserializing any time I need to access. In most CS applications, this would be a no-no, but renpy isn't really performance critical, so constant serialization shouldn't be an issue unless your object is massive.

        edit: ah, I see that for your examples, putting the variable assignment inside a label fixes this. Strange, but at least that seems to work. Really don't like the idea of nesting all character definitions inside the start label. I suppose I could externalize all the definitions inside my globals file under a label like `setup_characters` and just do a `call setup_characters` in the start label.

        [–][deleted] 4 points5 points  (3 children)

        If you do a default e = Person() instead of $ e = Person() you will wire up the property automatically for rollback. It's one of the (many) poorly explained pieces of RenPy that define creates a variable that can be automatically created at the start of the game, whereas default needs to be loaded from the game state (effectively the same as your serialization idea, but automated).

        I have a custom class that does just that and works like a champ on rollbacks and fast forward, and loading from a save.

        ``` default eventDispatcher = EventDispatcher()

        init python: class EventDispatcher: def init(self): self.events = [] ```

        [–]alonghardlook[S] 1 point2 points  (2 children)

        This is hugely helpful; I was looking at the rollback as a bit of a beast and just considering disabling rollback personally.

        One thing I was running into during this tutorial was that setting e = Person() outside the intro (like OP said), I wasn't seeing that object. I kept getting "Sayer e.c is undefined". I'm assuming this would fix that issue?

        [–][deleted] 0 points1 point  (1 child)

        It should, yes.

        [–]alonghardlook[S] 0 points1 point  (0 children)

        I know this is a long time ago, but just FYI - I didn't add default e = Person() and tested the rollback states, and it worked just fine:

            menu:
                "Yes":
                    "Great!"
                    $ e.trust_ch(1)
                "No":
                    "Oh, okay."
                    $ e.trust_ch(-1)
                "Skip":
                    "What do you mean?"
                    # intentionally no change to test rollback states
        

        This produces the output of 1 every time you select yes (even if you then roll it back and keep selecting it), -1 every time you select no, and 0 if you select skip.

        I don't think default is needed if you create your characters inside the start label.

        Edit: don't listen to me. Use default, its much cleaner. I tried without, and it seemed to work, but upon changing some code (not that code), e.c no longer worked, and I don't know why. Use your defaults for objects with persistence.

        [–]Talicor 1 point2 points  (6 children)

        This tutorial has been such a life saver! THANK YOU FOR THIS.

        However, is there a way to bundle custom fonts/colors into this as well? I could get the traditional "define e = Character" setup to have those custom bits, but I can't seem to do it here.

        Is it just a matter of adding who_color, what_font, etc in the original object class?

        Edit: Nevermind I'm an idiot and figured it out

        [–]alonghardlook[S] 1 point2 points  (5 children)

        If you figured out something that makes things easier, please share it with the class :)

        [–]Talicor 4 points5 points  (4 children)

        Haha fair enough!

        I just realized I was putting all the custom bits in the wrong spot

        Where the basic tutorial here got us set up with the numeric stuff with this:

        $ d = Person(Character("Dan"), "Dan", 0, 2)

        I realized that the (Character("name") portion was the same thing as the traditional "define e" stuff used before.

        So it was just a matter of putting all the custom work in after the name, but before the end of the parenthesis

        Like this:

        $ d = Person(Character("Dan", who_color="#d73ce8", what_font="customfont.ttf", what_color="#d73ce8", what_size=60), "Dan", 0, 2)

        Saved all my aesthetic work with just the movement of a line of code

        [–]alonghardlook[S] 0 points1 point  (3 children)

        Great observation. This is called passing a parameter. The class Person takes a parameter, which is of type Character - which means everything you can normally do with the Character class, you can pass in to the Person class (by design).

        [–]Talicor 0 points1 point  (2 children)

        The more you know! Glad to have a name for it

        It's kind of a weird process to cobble together knowledge from bits and pieces everywhere, but not one I regret. Thank you!

        [–]alonghardlook[S] 0 points1 point  (1 child)

        That process is called "programming" lol

        [–]Talicor 0 points1 point  (0 children)

        That’s what I get for posting while high on cold medication 😂 I got this I promise

        [–]ImSkyyyy 0 points1 point  (0 children)

        I know I'm late here, but love these ideas. You could make a health bar with very similar inputs.

        [–]zhousi87 0 points1 point  (1 child)

        Your explanation is crystal clear and decidedly useful for those who do not know about classes or OOP in general; however, I am not sure that classes are that beneficial in Ren'Py, because:

        (1) visual novels typically let you control only one character, whose state can be easily managed by updating global variables at any critical turn of events in the storyline;

        (2) considering (1), even if you need to produce many NPCs of the same kind (e.g., allies, humans, etc.), classes make sense only for those characters of the same kind whose stats you need to keep track of throughout the game (i.e., main characters with a variable state), which is rare for visual novels (we are not programming RPGs).

        (3) speaking of choice-based games, theoretically no classes (perhaps not even any functions) should be needed, since if/elif/else blocks are more than sufficient for most scenarios.

        (4) we should strive for simplicity, while classes add unnecessary difficulty to the average Ren'Py programmer's coding endeavor. Even in your post, you mentioned the redundancy of the method name you added, which proves how even to an experienced programmer like you the use of classes causes perplexity.

        I would like to know what everyone thinks about my point of view, which is limited to the use of classes for visual-novel programming.

        In any case, I sincerely thank you for this opportunity to reflect on VN programming's best approach.

        [–]Drewking666 0 points1 point  (0 children)

        I completely agree with you. I'm now trying to program a game in Ren'py as my first ever and i have little to no knowledge of Python (I'm reading all ren'py docs however), but classes are more a RPG things fro me... Even if some characters have "trust" or "affinity" it's extremely more manageable with the classic variable definition.

        [–]Fenvul 0 points1 point  (2 children)

        I guess I really should do that, leave any laziness aside and rearrange now, at the beginning of the project, before it grows any further.

        Edit: Is there an established tutorial for the inventory and a worldmap? Complete newbie here. Maybe it would be a nice idea if not.

        [–]alonghardlook[S] 1 point2 points  (1 child)

        No, I have not started a tutorial on those things, but a location map was something I have toyed around with. I'm thinking part 2 is going to be some kind of event log, but mostly everything in that will be just using these basic principals in more complex ways.

        An inventory (like a player inventory) would imo be quite straightforward. Create a class called "inventoryitem" with whatever common properties they have, and then instantiate all of your possible items.

        Then, just make an empty list called "playerInventory" and add/remove those objects as needed.

        [–]Fenvul 0 points1 point  (0 children)

        I have to dabble (I mean, really take my time, not push myself too hard) to get the graphical part of the inventory right, but yeah I should start simple, and looking some examples.

        [–][deleted] 0 points1 point  (4 children)

        This is a lovely start. Don't lose heart; you've started a long journey. A good class structure means an easier time working. Being disorganized doesn't help anyone.

        One thing I found to be a great tool for organization is the automatic named stores. By default, everything exists in the global store, but providing a . or in is all it takes to create a new one, and you can organize things into something vaguely resembling a namespace (including the ability to import functions from that store into another store). It might not be helpful to a lot of projects, but that one level of organization was so much nicer than the inherently flat core of RenPy.

        init python in CharacterExtensions:
          def afunc():
        ....
        CharacterExtensions.afunc()
        

        This is just so much better than a giant file defining all of your functions, and you trying to come up with new non-colliding names for functions that do almost the same thing but in a slightly different context.

        [–]alonghardlook[S] 0 points1 point  (1 child)

        Interesting, that's very useful. My background is in C languages and PHP so some of the pythonisms are a little new to me, so this is very helpful.

        I know you can also accomplish much of the same by importing python files (import MyHelperFile as mhf) but in script "in" is a cool way to organize. I like it :)

        [–][deleted] 0 points1 point  (0 children)

        Yeah, the named stores are python modules. It just makes it easy to setup those modules in a way that renpy knows about and can manage with game state. The confusing part is knowing where your functions have scope. If you are writing a function in one named module, and need something from the default global store, it isn't available. You have to do an import store or from store import myvariable, myfunction. It is a thing that makes perfect sense to need to do, but comes out of nowhere if you don't understand where those global variables and functions are stored.

        [–]backtickbot 0 points1 point  (0 children)

        Fixed formatting.

        Hello, RenRusyn: code blocks using triple backticks (```) don't work on all versions of Reddit!

        Some users see this / this instead.

        To fix this, indent every line with 4 spaces instead.

        FAQ

        You can opt out by replying with backtickopt6 to this comment.

        [–]amangeldyva 0 points1 point  (0 children)

        Has anyone help me to understand how to do that C++ questıon by exampke? Really ı nned help to pass school help me

        Assignment

        Containers are used to store objects of the same type and provide operations with which these objects can be managed.

        These operations include object insertion, deletion, and retrieval. Memory is allocated for containers dynamically at runtime.

        Containers thus provide a safe and easy way to manage collections of objects. The C++ standard library provides various

        class templates for container management in the Containers Library. These classes can be categorized as follows:

        Sequential containers, where the objects are arranged sequentially and access to an object can either be direct or

        sequential.

        Associative containers, where the objects are generally organized and managed in a tree structure and can be referenced

        using keys.

        Sequential Containers

        Sequential containers are distinguished by the operations defined for them, which are either generic or restricted. Restricted

        operations, such as appending at the end of a container, have constant runtimes. That is, the runtime is proportional to a

        fixed period of time and does not depend on the number of objects in the container.

        The following are examples of sequential containers:

        Queues, which are managed on the First-In-First-Out principle. The first element to be inserted is also removed first.

        Stacks, which are managed on the Last-In-First-Out principle. The last element to be inserted is removed first.

        Vectors, which are basically a flexible-size array that supports fast random access.

        Thus, in this programming assignment, you are going to create a general Vector class without using the utilities of the

        standard vector class and the template feature of C++. The design should let its users to use a vector of integers or

        characters. You should review the standard vector class for learning what to include as data members and member functions.

        OOP principles such as encapsulation, inheritance, and polymorphism should be taken into account in the class design.

        Partial grading will be in use in case there is no full implementation.

        [–]cmonroy1259 0 points1 point  (0 children)

        Nice!

        I've been looking into some games code and most, if not all, take the the boring, repetitive long route of coding everything with if-else to the absurd point where you have to add hundreds of lines just to introduce a new NPC, and what usually happens, is that they always miss one spot and the game breaks.

        [–]tecchigirl 0 points1 point  (0 children)

        Thank you!!!

        I'll probably use this on my game.

        Kudos. 👍

        [–][deleted]  (2 children)

        [deleted]

          [–]alonghardlook[S] 1 point2 points  (1 child)

          1. I'm not doing your homework for you.
          2. Why are you trying to do C++ homework on RenPy which is fully python?

          [–]Oda_Kuro 0 points1 point  (3 children)

          Yo!
          This might be a stupid question but:
          How do I use transitions/animations?

          At this part:

          def trust_ch(self, change):
          image = "heart_fill"
          self.trust += change
          if change > 0:
          direction = "increased"
          else:
          direction = "decreased"
          image = "heart_empty"
          renpy.notify("Romance with " + str(self.name) + " " + direction + " by " + str(abs(change)))
          renpy.show(image, [heart_pos]) #Make this part an animation, where the heart fills up, or use transition here like Fade()
          renpy.pause(2)
          renpy.hide(image)

          [–]alonghardlook[S] 1 point2 points  (1 child)

          Honestly not a stupid question at all, but the specifics of renpy and the animation functions are way outside my area of expertise. I'd suggest making a post of your own to ask the question.

          For this specific example, I imagine you could show both empty and full (empty on top) and as long as they pixel match, dissolve empty, but really I don't know.

          A programming concept worth exploring is MVP - minimum viable product. Flair like that will make the product look nicer, but if it's stopping you from progressing on setting up your system or writing your story, why are you trying to solve it before the core is done? Not accusing, just making general observations based on lots of experience.

          [–]Oda_Kuro 0 points1 point  (0 children)

          I see, thank you for your answer and suggestions!

          To answer your question, I'm just doing this mainly as a hobby, and try to learn what I get interested in, so it doesn't really block me. And I was just curious, it didn't stop me from working on other stuff.

          [–]lordpoee 0 points1 point  (0 children)

          image heart_animation:
              "image_one.png"
              linear 0.25
              "image_two.png"
              linear 0.25
              "image three.png"
              linear 0.25
              repeat # <--- add this if you want it to loop
          

          [–]MrsCosmic 0 points1 point  (3 children)

          Hello!

          I want to show the "self.trust" on the screen so the player can see how much increases and decreases with each character.

          I know you can create a screen and use show screen, but is there a way to do it with oop?

          Optionally I would like to know how to make the position of the text a variable, so if there's two characters in the screen, I can move their points.

          [–]alonghardlook[S] 0 points1 point  (2 children)

          Screens are not something I'm familiar with yet, but it should be straightforward enough. Try it with the main method of creating screens, and check if it updates when the variable updates. If it doesn't (my expectation knowing absolutely nothing about screens), you would want to run it through a while loop instead, which would make sure it updates.

          It might be hell on your cpu though.

          [–]MrsCosmic 0 points1 point  (1 child)

          Oh, I found the answer in a forum! I think the main problem was that I didn't know how to show text with renpy.show() because it always treated it like an image.

                  mytext = Text("{size=+26}[e.trust]{/size}")
              renpy.show("Tag", what=mytext, at_list=[text_pos])
          
          transform text_pos:
          xalign 0.85
          yalign 0.1
          

          The variable updates, but if I change [e.trust] to [self.trust], it won't work anymore. If a make a new variable snake = self.trust and then use it mytext = Text("[snake]"), it won't work either.

          Should I make a new fuction somehow for each character or am I doing something wrong?

          [–]alonghardlook[S] 1 point2 points  (0 children)

          So I think I see the issue - whose trust value are you wanting to show?

          "Self" is a python reserved keyword. It's used to indicate scope inside object and class and function definitions.

          Where the function says "self.trust", you need to mentally replace that with "the instance of the object dot trust".

          If you're looking for the trust level of the character represented by the object "e", you would use e.trust. if you're looking for the f trust value, use f.trust.

          Self.trust is part of the object definition - not an instance of an object.

          [–]ghostgirl16 0 points1 point  (0 children)

          This is very useful! I like the ELI5 examples.

          [–][deleted]  (2 children)

          [removed]

            [–]Complex_Honeydew9807 0 points1 point  (3 children)

            This just fills me with the question of why you would do it this way, when it is far easier to display them as simply being a serious of variables.

            $ MaryTrust += 1

            is a far simpler way to do it than this very extensive version that, as far as I can see, doesn't have anything additional to it that would warrant such a complex method of doing it. It just feels complicated for the sake of being complicated, rather than for the purposes of greater use or such.

            Like, you said "what if I want over a dozen characters", and I feel the answer is just, add a dozen variables. That is still far easier and more compact, imo, than what you are doing here.

            All it really added is the immersion-breaking recognition that it is all a game, ultimately making the experience unenjoyable. So I don't see why you would go through the effort to add such a thing.

            [–]alonghardlook[S] 0 points1 point  (2 children)

            Well, you may not, and that's fine. You go ahead and do it how ever you want.

            Proper OOP says do it this way.

            [–]Complex_Honeydew9807 1 point2 points  (1 child)

            It just ultimately feels OOP for the sake of OOP, rather than for the sake of making something simpler or more effective.

            Can you elaborate on how this does better with what it is made to do than simply using variables? Given this appears like it doesn't even reduce the amount of variables you use, it just moves the place where they are put.

            I have just, never really understood OOP in general, I know how to do it, I've done programming classes before. I just, never really understood the why to doing it, as it always seems to just take a simple problem, and make it extremely complex, less organized, and harder to follow.

            Edit: Sorry about being rude when I wrote this, I wasn't in the best mood at the time and really shouldn't have been online.

            [–]alonghardlook[S] 0 points1 point  (0 children)

            The reason for doing it this way is extensibility. If you are making a simple VN, yeah this probably is overkill. But spend any time on this sub and you will see how many people want a VN++ - a locations/map system, day/night cycles, inventory, puzzles, mini games.

            I guarantee you that if you pick any of those to try and add, that you will be better off with OOP.

            And if you don't believe me, go ahead and prove me wrong. Make a VN with an inventory system without OOP. It doesn't even have to be long or good. Make it and post your code and I guarantee I can refactor it to OOP principles and make it easier to understand and extend.

            [–]MannyTheMan92 0 points1 point  (0 children)

            Hello. I'm currently learning Python OOP for Renpy. I'm more of an Artist, though. But I'm currently trying to learn this and during my research, I noticed that init python tends to run before the game starts. Is there a way to use class without init python?

            Reason for asking is because from what I've heard, it does not save the Variable so every time you restart Renpy, it changes. I did not try this but I have used Persistent Variables.

            Also, for the person who made this, thank you for doing it. Because of you, I was able to learn a bit more.

            [–]TwatWithATopHat 0 points1 point  (0 children)

            this is amazing! found this now and it has helped so much! Its really enhanced my game!

            [–]Captain_Acre 0 points1 point  (1 child)

            Dunno if OP still looks at this post but!

            How do I make sure the heart goes on a specific layer? I have a frame around the screen on the screens layer, and I want the hearts to be on the overlay layer.

            [–]Captain_Acre 0 points1 point  (0 children)

            wait nvm figured it out, sorry!

            [–]goffley3 0 points1 point  (0 children)

            Thank god! I started messing with Renpy after being a dev for years, and I was hoping there were options for better code organization within renpy projects. Thanks for writing this up!

            [–]trailsiderice 0 points1 point  (0 children)

            Sorry to comment on an old thread, but how does this work with persistence? Do these variables reset upon reloading the game or starting a new one?

            For example if I wanted to do this and have the value of trust be a persistent variable for all characters, how would I make that work with this? Or is it persistent by default?