Ren'Py [Solved] Change variable based on time?

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
In Ren'Py I move characters around based on variables like:
Code:
$ location_sexygirl = "kitchen"
Unless the character is baked into the background-image itself during special events I use backgrounds + overlay-images to display them. Something like this works (pseudocode):
Code:
## screen displays all clickable/interaction/overlay-things
screen kitchen():
    tag buttons
    fixed:
        ## button to leave location
        imagebutton auto "gui/button%s.png" xpos 835-32 ypos 440-32 action Jump("go_bathroom") hover_sound "hover.opus"

        if location_sexygirl == "kitchen":
            image "images/ov kitchen sexygirl talk.png"
        if location_sexygirl == "kitchen_make_cookies":
            image "images/ov kitchen sexygirl make cookies.png"
        if location_sexygirl == "kitchen_eat_cookies":
            image "images/ov kitchen sexygirl eat cookies.png"
        if location_sexygirl == "kitchen_clean":
            image "images/ov kitchen sexygirl clean.png"
So I just talked to 'sexygirl' and I want her to start making cookies. At the end of a conversation I would set location_sexygirl to "kitchen_make_cookies". So far so good.

Now what I'd like to have is this:
-sexygirl automatically changes her location-variable after some time
-the player must be able to stop her during a certain state, lets just say for example - when she eats cookies we tell her she will get fat

Any idea on how to do this?
 

CeLezte

Member
Sep 10, 2017
234
147
It's a state machine problem:

You need to specify when the NPC has to move. Eg.: The MC enters the room, when exists the room. Or update location regardless which room is being visited or exited. If you want to make this time dependent then you need to figure out the following
and start tracking time in game.
Otherwise use somekind of a custom state for time such as:
Morning, Noon, Night, Midnight, etc.
 
Last edited:
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
I tried to use some of the Python time-function like $ t = time.time() or perf_counter() in Ren'Py and I always get:
'time' is not defined

Then I tried to use a python:-block instead of the one-line $ ... same thing.
Can somebody confirm this? Do I need to import some kind of Module/Package like mentioned here to use time?:


edit: never mind, I think I have got it...
init:
$ import time
 
Last edited:

Rich

Old Fart
Modder
Donor
Respected User
Game Developer
Jun 25, 2017
2,566
7,384
As @CeLezte says, you have a state machine (or a set of them) whether you realize it or not.
Code:
$ location_sexygirl = "kitchen"
is setting state - in this case, the location of the sexygirl.

Consciously thinking about things in terms of events and states will probably simplify things for you, even if you don't set up your code using FSM (Finite State Machine) logic explicitly. But however you code it, you still have concepts like "when girl eats all cookies" (an event) "change her image" (a portion of her overall state) "to fat" (a new state). Or, "when the game reaches noon" (event) "sexy girl's location" (location being another part of her state) "changes to kitchen".

So a character's overall state may consist of several sub-states - "fat/thin", "current location", "in love with / hates the MC". Similarly, the game itself has state (day/night) and events (random character shows up).

Given that, the overall concept of a state machine is "if I'm in this state" (either a micro state or the state of a character or the state of the entire game) "and this event happens, then change to this next state, creating some output as I do." In this case, the events can either be game-generated or events triggered by actions of your player, and "creating some output" could be a sequence of dialog. Then your "next state" helps govern what graphics and choices the player has next.

This is implicit in any game, but people don't usually explicitly think about it in that fashion. Doing so, however, can sometimes help you organize your code a bit more. In particular, you sometimes shift toward using data structures instead of explicit logic. As just one example, you could use a Python dictionary to relate an NPC's state to his/her image, rather than writing a lot of if/else logic, particularly if you need to do that in several places.
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
As you guys can probably tell by my previous post I'm a bloody beginner in Python. C++, C#, lua-script no problem - but I never ever touched Python in 20+ years.
I have an if-statement in my project that is 83 lines long. It handles moving the player to new locations. :FeelsBadMan:

Really appreciate the advice! I guess I could use Python dictionaries to put all possible location for a character in there. If the player changes his location I could just loop over it to display to correct images/screens/whatever.
 

Aeilion

Member
Jun 14, 2017
125
144
You can perhaps look at the function ui.timer for the idea of real time between two events/state and insert the changes you want.
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
Does ui.timer() even work? There is very little info about it, so I looked it up on GitHub:

Code:
FunctionStatementParser("timer", "ui.timer", 0)
Positional("delay")
Keyword("action")
Keyword("repeat")
and used it like this:
Code:
$ ui.timer( 5.0, ui.jumps("time_over"))
## or:
$ ui.timer( 5.0, ui.jumps("time_over"), False)
I could not make it jump, no matter what I tried. But even if it would work, I doubt it is useful for what I want. I need a timer that can do location/state changes kind of *in the the background* without interfering with whatever the player does in that moment.

The timer should be triggered by the player initially ... (following the example from post 1: conversation with sexygirl ends, her location/state gets changed and the timer starts) ... but then fire some events to move NPCs around on its own. So even if the player would just stand in the kitchen and do nothing after the dialog the sexygirl could walk of the screen and do something else for example.

I only can think of a partial solution using custom timer-functions that could only update() when the player interacts with something. timer.update() would then check its internal elapsed time and trigger NPC-movements to a new location/state. I'd place this update call here:
Code:
label adventure:
    $ renpy.block_rollback()
    $ call pseudecode_update_all_timers()
    pause
    jump adventure
When the player moves around freely in what I call 'adventure-mode' the timers would get updated on every player-interaction. Not perfect, because not realtime, but usable.

I think I am asking to much from Ren'py ... its not rly build for that.

EDIT:
Added testscript, should work if pasted in a new project, this moves sexygirl around every 10 seconds, but sadly not in realtime like I want.
Code:
init python:
    import time
    class Timer:
        def __init__(self):
            self.start_time = time.time()

        def restart(self):
            self.start_time = time.time()

        def stop(self):
            self.start_time = None

        def elapsed(self):
            if not self.start_time:
                return 0
            return time.time() - self.start_time

    timer = Timer()
    location_player = "kitchen"
    location_sexygirl = "kitchen"

define sexygirl = Character("sexygirl")

screen s_hud(timer):
    tag hud
    zorder 10
    $ elapsed = timer.elapsed()
    vbox at topright:
        text "elapsed time = [elapsed]  location_sexygirl = [location_sexygirl]"

screen s_kitchen():
    tag buttons
    vbox at truecenter:
        text "kitchen screen"
        textbutton "leave house" action Jump("go_outside")
        textbutton "have sexy time in the bedroom" action Jump("go_bedroom")

screen s_outside():
    tag buttons
    vbox at truecenter:
        text "outside screen"
        textbutton "go back in the house" action Jump("go_kitchen")

screen s_bedroom():
    tag buttons
    vbox at truecenter:
        text "outside screen"
        textbutton "go to kitchen" action Jump("go_kitchen")


label start:
    scene black
    sexygirl "Hello, I am here to test timing stuff. Where is my Delorean? Let's enter adventure-mode!"
    jump go_kitchen

label go_outside:
    $ location_player = "outside"
    jump go_player_location

label go_kitchen:
    $ location_player = "kitchen"
    jump go_player_location

label go_bedroom:
    $ location_player = "bedroom"
    jump go_player_location

label go_player_location:
    window hide
    show screen s_hud(timer)
    if location_player == "kitchen":
        ## TODO: add background here?
        scene black
        show screen s_kitchen
        if location_sexygirl == "kitchen":
            show sexygirl
    elif location_player == "outside":
        ## TODO: add background here?
        scene black
        show screen s_outside
        if location_sexygirl == "outside":
            show sexygirl
    elif location_player == "bedroom":
        ## TODO: add background here?
        scene black
        show screen s_bedroom
        if location_sexygirl == "bedroom":
            show sexygirl
    jump adventure

label adventure:
    $ renpy.block_rollback()
    call update_timers()
    pause
    jump adventure

label update_timers():
    python:
        elapsed_time = timer.elapsed()
        if elapsed_time >= 0 and elapsed_time < 10:
            location_sexygirl = "kitchen"
        elif elapsed_time >= 10 and elapsed_time < 20:
            location_sexygirl = "bedroom"
        elif elapsed_time >= 20 and elapsed_time < 30:
            location_sexygirl = "outside"
        else:
            timer.restart();
            location_sexygirl = "kitchen"
    return
 
Last edited:

Aeilion

Member
Jun 14, 2017
125
144
I was thinking of something like this :

python:
def eatcookies():
global location_sexygirl
location_sexygirl = kitchen_eat_cookies
return

def changepos():
if location_sexygirl == "kitchen_make_cookies":
ui.timer(60.0, eatcookies)

And you call your function changepos() when you want...
You can add "jump" or other things too.

For me, there is no problem it works very well.
 
  • Like
Reactions: f95zoneuser463

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,979
16,236
Er... Am I the only one here which know about and its friend config.periodic_callbacks (alright, it's a list, but it's not documented (anymore?) ) ?
 
  • Like
Reactions: f95zoneuser463

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,979
16,236
[Sorry for the double post, but it's seem a bit late to edit it]

So, for a working timer on Ren'py, you can read this .
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
I was busy the last days implementing a new questlog (modified version of ) and now I tried ui.timer() again:
Code:
init python:
    import time
    time_start = None
    triggered = False

    def trigger_function():
        print("running: trigger_function")
        triggered = True
        return

    def start_timed_trigger():
        print("running: start_timed_trigger")
        
        # does nothing:
        #ui.timer(5.0, trigger_function)

        # causes an exception: "A timer must have an action supplied."
        #ui.timer(5.0, trigger_function())
        
        # ONLY works if the user does no interactions for 5 seconds
        ui.timer(5.0, ui.jumps("trigger_label"))
        
        return time.time()

screen info():
    vbox at truecenter:
        if time_start != None:
            $elapsed_time = time.time() - time_start
            text "time elapsed = [elapsed_time]"
        text "triggered = [triggered]"

label start:
    $ time_start = start_timed_trigger()
    jump adventure

label adventure:
    $ renpy.block_rollback()
    show screen info
    pause
    jump adventure

label trigger_label:
    $ print("running: trigger_label")
    $ triggered = True
    "trigger_label got triggered"
This only works for me if the user does not interact/click for 5 seconds. And I also don't know how to make it use a python function directly. This seems to be designed for quicktime-events and would only be useful to me if I can make it run a python function in the background ... jumping to labels breaks the *do not interfere with whatever the player is doing*-rule. (I'm not sure whether I explain this right with my english) But this is a dead-end anyway, the player must be able to interact all the time.

On what anne O'nymous linked - I just had a very quick loot at it and this may indeed be very helpful. However I moved this problem down on my prioritylist because its taking up waaaay to much time.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,979
16,236
[...] and now I tried ui.timer() again:
Like said in the old version of the documentation, "most of the UI functions create widgets and add them to the screen". This is because they are the ancestors to all the screen language (which mostly still use them behind the curtain of modesty which is the said screen language).
It doesn't mean that you can't use them, but that they can and will have side effect, like you discovered it with your tests. And anyway it mainly mean that they don't works like you seem to think.


Code:
        # does nothing:
        #ui.timer(5.0, trigger_function)
It do something. It create a timer object (precisely a renpy.display.behavior.Timer object). But this timer object isn't started at creation time. It will be started later, when the first event will happen, so probably when the screen will be displayed.


Code:
        # causes an exception: "A timer must have an action supplied."
        #ui.timer(5.0, trigger_function())
Because you provide it the return value of the trigger_function code, which here is None, instead of a "reference" to the said code.


Code:
        # ONLY works if the user does no interactions for 5 seconds
        ui.timer(5.0, ui.jumps("trigger_label"))
1) It don't throw an exception because ui.jumps return a callable value, unlike in the previous try. Note that it wouldn't have thrown an exception even if ui.jumps returned something not callable. None is the only value which can throw the exception, anything else is assumed as a valid value until it, possibly, throw an exception when Ren'py will try to execute the action.

2) The result shouldn't have differed from your first try, because the timer is still not started at creation time ; which is the main problem of your code.
So, here I'll make a pure guess. I assume that some magic or weird none breaking bug, happen when you call ui.jumps. Something which trick Ren'py, making it think that an event happened and so make it call the event method of the timer object.


This only works for me if the user does not interact/click for 5 seconds.
Which is normal. Your code create a screen widget... but attach it to no screen. So, by magic it's attached to the actual screen, which is instantly reset when the interaction end. In other words, once the player interact with the game, the timer disappear.


And I also don't know how to make it use a python function directly.
The first syntax is 100% correct. But like I said, it don't start the timer. That's this last part which cause your code to not works, not the syntax you used.


This seems to be designed for quicktime-events and would only be useful to me if I can make it run a python function in the background ... jumping to labels breaks the *do not interfere with whatever the player is doing*-rule.
No, it's designed for screen, so either to be silently used by the screen language or put in a Python defined screen.


On what anne O'nymous linked - I just had a very quick loot at it and this may indeed be very helpful.
It will be helpful. Well, at least it's my intend when writing it ;)


However I moved this problem down on my prioritylist because its taking up waaaay to much time.
Now that you understand why your code don't works, you can put it back on the top of the list. It's not difficult at all to do what you want:

Pure Python - basic approach:
Code:
init python:
    intervalCounter = 0

    def myTimer():
        store.intervalCounter += 1
        # Replace 20 by 20 x number of seconds before event
        if intervalCounter < 20: return
        store.intervalCounter = 0
        [do what you want here]

label someLabel:
    [...]
    # here you start the timer
    $ config.periodic_callbacks.append( myTimer )
    [...]
    # here you stop the timer
    $ config.periodic_callbacks.remove( myTimer )
    [...]
Pure Ren'py - basic approach:
Code:
init python:
    def myFunction():
        [do what you want]

screen myTimer( itv, fnct ):
    timer itv action Function( fnct )

label someLabel:
    [...]
    # here you start the timer for 1 second
    show screen myTimer( 1, myFunction )
    [...]
    # here you stop the timer
    hide screen myTimer
    [...]
 
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
Okay thanks to you, anne O'nymous, this *seems* to be the solutions for me:

Code:
init python:
    # state = [imagetype]_[location]_[room]_[npc-name]_[outfit]_[anim]
    # state = ov_home_bedroomgirls_emily_casual_change
    state_sexygirl = "leftroom" # yes I decided to rename that, state is shorter and more fitting than location
    sexygirl_move_timer = 0.0

    class Timer( renpy.store.object ):
        def __init__( self, interval, fnct ):
            self._seconds_elapsed = 0
            self._interval = interval
            self._fnct = fnct
            self.__ticks = int( interval * 20 )

        def __call__( self ):
            self._seconds_elapsed += 0.05 # add 1/20th of a second
            self.__ticks -= 1
            if self.__ticks > 0: return
            self.__ticks = int( self._interval * 20 )
            if callable( self._fnct ): self._fnct()

        def seconds_elapsed(self):
            return self._seconds_elapsed

    # This function shall be responsible for all NPC states (location,outfit,animations)
    # It will be called every seconds (for now).
    # It MUST NOT have any side-effects on the player!
    # We don't want the NPC to walk away during a conversation.
    def update_npc_states():
        print("update_npc_states " + str(timer.seconds_elapsed()) + " seconds elapsed")
        
        # say screen visible? do nothing!
        if renpy.get_screen("say"):
            return

        # TODO: move NPCs based on quest-progress, time, playerlocation, etc
        # Move sexygirl every 10 seconds to between left and right room to test
        store.sexygirl_move_timer += 1
        if store.sexygirl_move_timer > 9:
            store.sexygirl_move_timer = 0
            if store.state_sexygirl == "rightroom":
                store.state_sexygirl = "leftroom"
            else:
                store.state_sexygirl = "rightroom"

        # update screens to reflect changes.
        # I think this is necessary. Otherwise the player can click on 'talk'-buttons even if a ...
        # NPC already left the screen.
        renpy.restart_interaction()
        return

    timer = Timer( 1.0, update_npc_states )
    config.periodic_callbacks.append( timer )

define sexygirl = Character("sexygirl")

screen s_info():
    tag hud
    zorder 10
    text "seconds elapsed = " + str(timer.seconds_elapsed()) + ", state_sexygirl = [state_sexygirl]"

screen s_leftroom():
    tag left
    if state_sexygirl == "leftroom":
        image "gui/window_icon.png" at left # represents sexygirl
        textbutton "talk" action Jump("talk_sexygirl_left") at left

screen s_rightroom():
    tag right
    if state_sexygirl == "rightroom":
        image "gui/window_icon.png" at right # represents sexygirl

label start:
    show black
    jump adventure
    return

label adventure:
    window hide
    show screen s_info
    show screen s_leftroom
    show screen s_rightroom
    pause
    jump adventure

label talk_sexygirl_left():
    sexygirl "While talking to me the npc-state MUST NOT change in 'update_npc_states'"
    sexygirl "If everything work as intended I should stay here while I talk to you and walk away after some time if our dialog ends."
    jump adventure
This needs testing in the 'big' project now. I'm worried about side-effects. In theory it should allow me to move around NPCs in the world freely.

One thing I noticed while fiddling with this:
It is not reliable/precise as 'real' timer. I was rendering in the background and my system got so slow that it took ren'py over 10 seconds to show just 1 second passed. But this was a very extreme scenario and not a problem for what I need it for.

I read your other post here ... (which my brain is still processing)

... and was worried about the NPC-states not being saved, but I guess by calling renpy.restart_interaction() this counts as interaction and it works. Now I just have to see whether restart_interaction() will causes any side-effects in a real project.

Before I try this I think I'm going to make a backup of my project now. I'd like to rename all my images to follow a specific naming-scheme. That name shall then also represent the NPC-state used as string in Ren'Py-script. Including 3D-source-files, high-res master-images, actual-ingame-images and strings in the scripts I will probably have to rename more than 1000 things now ... this will take a while. :oops: If there are any other issues I will report again.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,979
16,236
Code:
        # say screen visible? do nothing!
        if renpy.get_screen("say"):
            return
If this is to prevent the girl to change room when the player talk to her, there's probably a better way to do this.
The say screen is the most used of all. In an average game it's seen 90% of the time.
Haven't had to use it yet, but , with perhaps the help of seem to be a better solution here. You'll have to make some tests since it probably don't works as you expect and definitively not exactly as the documentation say.
Lets take this ( first picture for Anna showering in the bathroom):
Code:
    scene bathroom anna shower 1
Here, "bathroom" is the tag, and "anna", "shower" and "1" are the attributes. Warning, depending of the function used, they are either returned in order of use, alphabetical order, or random order.
So, lets say you want to pause the timer if Anna is in the bathroom, you'll have to do something like:
Code:
    # If it's a bathroom related picture
    if "bathroom" in renpy.get_showing_tags():
        # And it it have anna on it
        if "anna" in renpy.get_attributes( "bathroom" ):
             return
And if you want to pause the action when she's only showering :
Code:
    # If it's a bathroom related picture
    if "bathroom" in renpy.get_showing_tags():
        # And it it have anna on it
        if "anna" in renpy.get_attributes( "bathroom" ):
            # And she's showering
            if "showering" in renpy.get_attributes( "bathroom" ):
                 return
And if you works with "bathroom_anna_showering_1", it should be more something like :
Code:
    tags = renpy.get_showing_tags()
    for t in tags:
        # Don't have "bathroom" in it, so next please
        if t.find( "bathroom" ) = -1: continue
        # Don't have "anna" in it, so next please
        if t.find( "anna" ) = -1: continue
        # Don't have "showering" in it, so next please
        if t.find( "showering" ) = -1: continue
        # it's in the bathroom, with Anna and she's showering
        return
Obviously there's better way to write these codes. Here it's the most explicitly self explaining syntax, to help you understand what should be done.
In the end the principle is to take the most restrictive condition possible to avoid side effects. And this condition is to check the picture displayed.


Code:
        # update screens to reflect changes.
        # I think this is necessary. Otherwise the player can click on 'talk'-buttons even if a ...
        # NPC already left the screen.
        renpy.restart_interaction()
That's exactly that.


This needs testing in the 'big' project now. I'm worried about sideeffects.
The best solution is to have a "myTest.rpy" file, and a "myTest" label in it. You change whatever behind this label for each test you want to perform, and add a "jump myTest" somewhere in your "start" label when you want to perform a test.
As for the test themselves, think about the possible side effects, explicitly create them, and look how your code react to them ; adjusting the code if needed.
Then you can perform a test with the real game now that you have a more "side effect proof" code.


One thing I noticed while fiddling with this:
It is not reliable/precise as 'real' timer. I was rendering in the background and my system got so slow that it took ren'py over 10 seconds to show just 1 second passed. But this was a very extreme scenario and not a problem for what I need it for.
Yes, it's because the game's process is called less often, and did not anymore reflect real time.
Basically speaking imagine that it works like this :
- Every single X unit of time, the OS call the game's process and let him perform few actions before switching to another process.
- Every single Y actions performed, the game's process call the timers (whatever the Ren'py ones or the one in config.periodic_callback(s)).
So, If the game's process is called less often, then the timers are updated less often themselves.

But honestly most of coder don't care about this, unless they need a real time timer (which happen only on critical system, and those one don't goes slow this way because they generally don't perform other tasks). Simply because this time slowing situation do not just concern the timer, but the whole code. Here your timer gone slow, but if the player clicked to show the next dialog line, well this said dialog line would have also took an eternity to be displayed. So in the end the timer is reliable in regard of the game itself, and that's what matter the most here.
 
  • Like
Reactions: f95zoneuser463

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
I'm still in the process of renaming/reorganizing my stuff. I tested the Timer last evening by randomizing a NPC state every second. It moves around in the world from the beach, to the kitchen, bedroom, etc... At that point I noticed that the graphics would not update because the overlays to display the NPC where not in a screen. They where displayed somewhere else using show/scene/hide.
I believe this is the correct way to do things. Ren'py-Screen-docs:
The things that a user sees when looking at a Ren'Py game can be divided into images and user interface. Images are displayed to the user using the scene, show, and hide statements, and are generally part of the story being told. Everything else the user sees is part of the user interface, which is customized using screens.
That is exactly how I used screens in the past, but with the Timer? ... I guess I have to add all graphics (indirectly changed by the Timer) to a Screen.
To test more I moved the overlay-graphics for the NPC to the screens that I normally only use to display clickable buttons over a location. It worked and the NPC started moving. Everything fixed?
Not quite. This breaks the 'H'-key to hide the interface. Problem is now that the NPC would disappear when pressing 'H'. It is considered part of the interface.

I'm not sure how to solve this yet. I don't want the hide-interface-function to break. Docs for 'H'-keybind:

#default keymap in contained inside renpy/common/00keymap.rpy (v6.99)
Code:
config.keymap = dict(
    # Bindings present almost everywhere, unless explicitly disabled.
    ...
    hide_windows = [ 'mouseup_2', 'h' ],
    ...
So this is where I'm currently at. Not sure how to continue ... but I'll finish other stuff first and I'm sure I'll figure this out.

About the if renpy.get_screen("say"): line to block the NPC-state-updates:
This actually works absolutely fine for my case. The player can walk around freely in my game where he would not talk to anyone. A bit like "Summertime Saga" (just a not the art-style) where the mc can move from location to location and I'm also working on a map.

I marked this thread as solved. Thanks again for the help!
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,979
16,236
[...]At that point I noticed that the graphics would not update because the overlays to display the NPC where not in a screen. They where displayed somewhere else using show/scene/hide.
You have the word, I underlined it, you just miss the configuration related values:
and . Both are list, so just append to them.
Code:
   config.overlay_screens.append( "myOverlay" )
The difference between a screen and an overlay is subtle. A screen is a full part of the display, while an overlay is an addition to it.

There's multiple possibilities, the simplest is probably something like this :
Code:
screen myOverlay:
    add "icon/thisCharacterIcon.png":
        if character.location == "beach":
            xpos 100
            ypos 100
        elif character.location == "house":
            xpos 500
            ypos 500
that You can simplify like this if you really have many locations :
Code:
init python:
    charXPos = { "beach": 100, "house": 500 }
    charYPos = { "beach": 100, "house": 500 }
screen myOverlay:
    add "icon/thisCharacterIcon.png":
        xpos charXPos[character.location]
        ypos charYPos[character.location]
And finally if you more than one character can be at the same location in the same time, you can do like this :
Code:
init python:
    charXPos = { "beach": 100, "house": 500 }
    charYPos = { "beach": 100, "house": 500 }
screen myOverlay:
    add "icon/firstCharacterIcon.png":
        xpos charXPos[character.location]
        ypos charYPos[character.location]
    add "icon/secondCharacterIcon.png":
        xpos charXPos[character.location] + 20
        ypos charYPos[character.location]
charXPos and charYpos point the base position of the icon, and each character have its own position relative to this one, that you add.
You can even do it with just four lines:
Code:
init python:
    mainXPos = { "beach": 100, "house": 500 }
    mainYPos = { "beach": 100, "house": 500 }
    allMyChars = [ "char1", "char2", "char3" ]
    charPicture = { "char1": "icon/firstCharacterIcon.png" [...] }
    charAdjustX = { "char1": 0, "char2": 20, "char3": 40 }
    def charLocation( char ):
         [Here you return the string representing the location of the given character]
screen myOverlay:
    for char in allMyChars:
        add charPicture[char]:
            xpos mainXPos[charLocation(char)] + charAdjustX[char]
            ypos mainYPos[charLocation(char)]
If you use object to represent all the character you can directly put charPicture, charAdjustX and charLocation in them to simply even more.


Not quite. This breaks the 'H'-key to hide the interface. Problem is now that the NPC would disappear when pressing 'H'. It is considered part of the interface.
Which is normal from my point of view since they effectively are part of the user interface. Now, the question is: is this really an issue ?
The player will hit "h" to hide the interface and look at the beautiful picture on the screen, he will probably never do it when looking at the map, as sexy and alluring that the said map can be.


I marked this thread as solved. Thanks again for the help!
You're welcome.
 

f95zoneuser463

Member
Game Developer
Aug 14, 2017
219
1,024
TLDR:
Use layer "master" one a screen to make the 'H'-key not hide it.

Just posting this for the record in case other people have similar problems ...

My solution for the 'H'-key problem to hide to interface correctly:
For every locations the player can visit in my game two screens are defined.
  • One master-screen that holds all displayables that are not part of the interface. (For example I have some dirt that the player must clean for a quest on a master-screen, or some animations) I use the prefix "sm_"<screenname> for all of them. These must be placed on the "master" layer of Ren'py by using layer "master" and tagged as master.
  • One hud-screen that holds all interface-elements to interact with things (door-buttons, talk-buttons). I use the prefix "s_<screenname>" for all of them. These are tagged as hud. During dialogs I simply hide the hud-screens to stop the player from escaping from the dialog. Otherwise he could just click on a door for example and that would break the dialog in my game.
The layer "master" line is the key here. It causes Ren'Py to not see this screen as part of the interface.

If a player moves to a new location everything is shown like:
Code:
## this code is run if the player-location/state changes:
## hud_interface with zorder 10 to be on-top, it is hud to show location-independent ...
## buttons like the quest journal and map
## this line is here to always show the hud again after the player was in a dialog where it was hidden
show screen hud_interface
...
*player-location-logic-code to find out which location to show*
...
## display example location
scene bg example # clears layer and shows new background
show screem sm_bg_example # show master screen as part of scene
show screen s_bg_example # show hud for location
...
jump to_a_loop_with_pause_to_allow_free_movement
I really like it this way. Everything is organized very nice.