Ren'Py How to add realistic camera shaking as a custom preference that will be change when player changes it

Luna Granger

New Member
Aug 30, 2018
13
8
41
I'm trying to add a custom preference that will be change immediately when player changes it. This custom preference makes screen shaking smoothly, its making you like you're seeing a real scene. However I managed to handle this shake effect. But I can't toggle it when player turns it on or off. I tried so many things and I made it but it needs to check toggle at every page that if its on or off, and its resetting transition loop so its not good and I don't want this. Can somebody help me how can I toggle this on or off when player changes it?

Python:
script.rpy

init python:
    if not hasattr(persistent, "shake_enabled"):
        persistent.shake_enabled = True
    
    def auto_update_camera_shake():
        if persistent.shake_enabled:
            renpy.show_layer_at(camshake, layer="master")
        else:
            renpy.show_layer_at(reset_camera, layer="master")
    
    config.interact_callbacks.append(auto_update_camera_shake)
    
    def update_camera_shake():
        auto_update_camera_shake()

# Transforms
transform camshake:
    ease 1.2 xpos 0 ypos 0
    ease 1.2 xpos -5 ypos 3
    ease 1.2 xpos 4 ypos -2
    ease 1.2 xpos 3 ypos 5
    ease 1.2 xpos -4 ypos -3
    repeat

transform reset_camera:
    xpos 0 ypos 0
    
screens.rpy

vbox:
    style_prefix "radio"
    label _("Realistic Camera Shake Effect")
    textbutton _("ON") action [
        SetField(persistent, "shake_enabled", True),
        Function(update_camera_shake)
    ]
    textbutton _("OFF") action [
        SetField(persistent, "shake_enabled", False),
        Function(update_camera_shake)
    ]
 

StGiggles

New Member
May 9, 2024
4
9
13
If I understood correctly you want something like this:

Code:
label start:
    camera at reset_camera
    screen camshaker:
        vbox:
            style_prefix "radio"
            label _("Realistic Camera Shake Effect")
            textbutton _("ON") action [
                SetVariable("shake_enabled", True),
                Function(renpy.show_layer_at, camshake, camera=True, reset=False)
            ]
            textbutton _("OFF") action [
                SetVariable("shake_enabled", False),
                Function(renpy.show_layer_at, reset_camera,  camera=True, reset=False)
            ]
            
    show screen camshaker
 
  • Like
Reactions: Luna Granger

Luna Granger

New Member
Aug 30, 2018
13
8
41
If I understood correctly you want something like this:

Code:
label start:
    camera at reset_camera
    screen camshaker:
        vbox:
            style_prefix "radio"
            label _("Realistic Camera Shake Effect")
            textbutton _("ON") action [
                SetVariable("shake_enabled", True),
                Function(renpy.show_layer_at, camshake, camera=True, reset=False)
            ]
            textbutton _("OFF") action [
                SetVariable("shake_enabled", False),
                Function(renpy.show_layer_at, reset_camera,  camera=True, reset=False)
            ]
           
    show screen camshaker
Yes, it works perfectly in in-game HUD! Thank you. But it didn't work when I try this in preferences menu. Is there any chance to put this in preferences menu?
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,725
20,887
1,026
Code:
label start:
    camera at reset_camera
    screen camshaker:
No, no and no.

screen is a first level statement, similar to label or init by example. Placing it in a label bloc will just break the said bloc , leading Ren'Py to assume that the label stop where the screen statement is encountered. This while lead to a label, followed by an orphan bloc.
The way Ren'Py works mean that, in absence of actual branching it just continue to browse the AST line by line, and therefore will proceed the said orphan bloc. But this is more a design flaw than an actually wanted feature. So relying on it is really not a good idea.


Code:
                Function(renpy.show_layer_at, camshake, camera=True, reset=False)
Not really sure that the state is included in the saved data. Therefore if possibly need to rely on a after_load label to restore the state:
Code:
label after_load:
    if shake_enabled:
        $ renpy.show_layer_at( camshake, camera=True, reset=False )
    else:
        $ renpy.show_layer_at( reset_camera,  camera=True, reset=False )
    return
In such case, players will also encounter an issue. In case they are in a multi-playthrough, they'll have to change the value for all of them. What mean that the flag should be stored in a persistent value, not a context based one.
The action then should be SetField( persistent, "shake_enabled", True ) and SetField( persistent, "shake_enabled", False ), instead of the current SetVariable.
 

StGiggles

New Member
May 9, 2024
4
9
13
No, no and no.

screen is a first level statement, similar to label or init by example. Placing it in a label bloc will just break the said bloc , leading Ren'Py to assume that the label stop where the screen statement is encountered. This while lead to a label, followed by an orphan bloc.
The way Ren'Py works mean that, in absence of actual branching it just continue to browse the AST line by line, and therefore will proceed the said orphan bloc. But this is more a design flaw than an actually wanted feature. So relying on it is really not a good idea.
Yeah thanks that makes sense.

Not really sure that the state is included in the saved data. Therefore if possibly need to rely on a after_load label to restore the state:
Code:
label after_load:
    if shake_enabled:
        $ renpy.show_layer_at( camshake, camera=True, reset=False )
    else:
        $ renpy.show_layer_at( reset_camera,  camera=True, reset=False )
    return
In such case, players will also encounter an issue. In case they are in a multi-playthrough, they'll have to change the value for all of them. What mean that the flag should be stored in a persistent value, not a context based one.
The action then should be SetField( persistent, "shake_enabled", True ) and SetField( persistent, "shake_enabled", False ), instead of the current SetVariable.
Alternatively if we default the variable using SetVariable would be fine right?
 
  • Like
Reactions: Luna Granger

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
12,725
20,887
1,026
Alternatively if we default the variable using SetVariable would be fine right?
No, the issue here is that "shake_enabled" is limited to the current play. If the player have more than one play, if he decide to restart the game, or if he have to load a save prior to the change, he'll loose his choice for the shaking effect and have to define it again.
When a flag, and more globally a variable, is used for a configuration-like purpose, it must be defined as , in order for its value to apply independently from the context.