Skip to content

Integration Examples

Norz3n edited this page Dec 10, 2025 · 2 revisions

Integration Examples

Real-world patterns for integrating the Dynamic Ambient System into your Ren'Py project.


Main Menu Integration

Basic Setup

# ambient_auto_start.rpy handles this automatically
# Just configure audio_assets.yaml:

main_theme:
  file: "audio/main_theme.mp3"
  duration: 45
  after_theme_arrangement: "main_menu"

Custom Main Menu

screen main_menu():
    tag menu
    
    on "show" action Function(start_menu_ambient)
    on "hide" action Function(stop_menu_ambient)
    
    # ... your menu content

init python:
    def start_menu_ambient():
        if not ambient.is_active:
            ambient.start_with_main_theme()
    
    def stop_menu_ambient():
        ambient.stop_ambient()

Location-Based Ambient

Simple Location Switch

label forest:
    scene bg forest
    ambient play "forest_day"
    
    "You enter the forest."
    
    jump forest_explore

label city:
    scene bg city_street
    ambient play "city_daytime"
    
    "The city bustles around you."
    
    jump city_explore

Location with Time of Day

# arrangements.yaml
arrangements:
  forest_morning:
    tracks:
      forest_base: { volume: 0.5 }
      morning_birds: { volume: 0.6 }

  forest_day:
    tracks:
      forest_base: { volume: 0.6 }
      birds: { volume: 0.5 }
      insects: { volume: 0.3 }

  forest_evening:
    tracks:
      forest_base: { volume: 0.5 }
      crickets: { volume: 0.4 }

  forest_night:
    tracks:
      forest_base: { volume: 0.4 }
      night_insects: { volume: 0.5 }
      owl: { volume: 0.3 }
label enter_forest:
    scene bg forest
    
    # Play appropriate arrangement based on time
    if time_of_day == "morning":
        ambient play "forest_morning"
    elif time_of_day == "day":
        ambient play "forest_day"
    elif time_of_day == "evening":
        ambient play "forest_evening"
    else:
        ambient play "forest_night"
    
    jump forest_main

Weather System

Configuration

# arrangements.yaml
arrangements:
  outdoor_clear:
    tracks:
      nature_base: { volume: 0.5 }
      birds: { volume: 0.4 }
    layers:
      light_rain:
        rain_light: { volume: 0.5 }
        rain_drops: { volume: 0.3 }
      heavy_rain:
        rain_heavy: { volume: 0.8 }
        rain_drops: { volume: 0.5 }
      storm:
        thunder: { volume: 0.7 }
        wind_strong: { volume: 0.6 }

Weather Controller

init python:
    def set_weather(weather_type):
        """Update ambient based on weather."""
        # Clear all weather layers first
        ambient.set_layer("light_rain", False, fade_time=2.0)
        ambient.set_layer("heavy_rain", False, fade_time=2.0)
        ambient.set_layer("storm", False, fade_time=2.0)
        
        # Apply new weather
        if weather_type == "light_rain":
            ambient.set_layer("light_rain", True, fade_time=3.0)
        elif weather_type == "heavy_rain":
            ambient.set_layer("heavy_rain", True, fade_time=3.0)
        elif weather_type == "storm":
            ambient.set_layer("heavy_rain", True, fade_time=2.0)
            ambient.set_layer("storm", True, fade_time=3.0)

label outdoor_scene:
    ambient play "outdoor_clear"
    
    "A beautiful day outside."
    
    $ set_weather("light_rain")
    "It starts to drizzle."
    
    $ set_weather("heavy_rain")
    "The rain intensifies."
    
    $ set_weather("storm")
    "A storm rolls in!"
    
    $ set_weather("clear")
    "The storm passes."

Tension and Combat

Configuration

# arrangements.yaml
arrangements:
  exploration:
    tracks:
      ambient_base: { volume: 0.5 }
      environment: { volume: 0.4 }

  tension_low:
    tracks:
      ambient_base: { volume: 0.4 }
      tension_drone: { volume: 0.3 }

  tension_high:
    tracks:
      tension_drone: { volume: 0.6 }
      heartbeat: { volume: 0.4 }
      stinger: { volume: 0.3 }

  combat:
    tracks:
      combat_music: { volume: 0.8, type: "mandatory" }
      combat_hits: { volume: 0.5 }

Tension System

init python:
    tension_level = 0
    
    def update_tension(level):
        global tension_level
        tension_level = level
        
        if level == 0:
            ambient.play_arrangement("exploration", fade_time=3.0)
        elif level == 1:
            ambient.play_arrangement("tension_low", fade_time=2.0)
        elif level == 2:
            ambient.play_arrangement("tension_high", fade_time=1.5)
        elif level >= 3:
            ambient.play_arrangement("combat", fade_time=1.0)

label encounter_enemy:
    $ update_tension(1)
    "You sense danger nearby..."
    
    $ update_tension(2)
    "An enemy appears!"
    
    menu:
        "Fight":
            $ update_tension(3)
            jump combat_scene
        "Flee":
            $ update_tension(0)
            jump escape_scene

Indoor/Outdoor Transitions

Configuration

arrangements:
  outdoor_street:
    tracks:
      traffic: { volume: 0.5 }
      crowd: { volume: 0.4 }
      birds: { volume: 0.3 }

  indoor_cafe:
    tracks:
      cafe_ambience: { volume: 0.5 }
      coffee_machine: { volume: 0.3 }
      quiet_chatter: { volume: 0.4 }
    layers:
      music:
        cafe_music: { volume: 0.3 }

  indoor_office:
    tracks:
      office_hum: { volume: 0.4 }
      keyboard_typing: { volume: 0.3 }
      ac_unit: { volume: 0.2 }

Transition Handler

init python:
    current_location_type = "outdoor"
    
    def enter_location(location, location_type):
        global current_location_type
        
        # Determine fade time based on transition type
        if current_location_type != location_type:
            fade = 2.0  # Longer fade for indoor/outdoor change
        else:
            fade = 1.0  # Shorter fade for same type
        
        ambient.play_arrangement(location, fade_time=fade)
        current_location_type = location_type

label street:
    scene bg street
    $ enter_location("outdoor_street", "outdoor")
    "You walk down the busy street."

label enter_cafe:
    scene bg cafe
    $ enter_location("indoor_cafe", "indoor")
    ambient layer "music" on
    "You step into the cozy cafe."

label enter_office:
    scene bg office
    $ enter_location("indoor_office", "indoor")
    "The office is quiet today."

Dialogue Volume Ducking

Category-Based Ducking (Recommended)

Duck only ambient sounds while keeping music at full volume (or vice versa).

init python:
    original_ambient_vol = 0.7

    def duck_ambient_only():
        global original_ambient_vol
        # Store current ambient base volume
        original_ambient_vol = ambient.base_volume_ambient
        # Duck ONLY ambient sounds (wind, birds, etc.)
        ambient.set_base_volume(original_ambient_vol * 0.4, "ambient")

    def restore_ambient_only():
        ambient.set_base_volume(original_ambient_vol, "ambient")

label important_dialogue:
    $ duck_ambient_only()
    
    voice "audio/voice/important_line.ogg"
    "This line is heard clearly over the quiet wind."
    
    $ restore_ambient_only()

Global Ducking

Duck everything (Music + Ambient).

init python:
    original_volume_mult = 1.0
    
    def duck_all():
        # Duck EVERYTHING (Music and Ambient)
        ambient.set_base_volume(0.3)
    
    def restore_all():
        ambient.set_base_volume(1.0)

Character-Based Ducking

init python:
    def character_callback(event, interact=True, **kwargs):
        if event == "begin":
            # Duck ambient only when this character speaks
            ambient.set_base_volume(0.4, "ambient")
        elif event == "end":
            ambient.set_base_volume(0.7, "ambient")

define narrator = Character(None, callback=character_callback)

Save/Load Compatibility

The system automatically handles save/load through __getstate__ and __setstate__. However, for complex setups:

init python:
    def after_load_callback():
        """Restore ambient state after loading."""
        # The system auto-restores, but you can add custom logic
        if hasattr(store, 'current_ambient_arrangement'):
            if store.current_ambient_arrangement:
                ambient.play_arrangement(store.current_ambient_arrangement)
    
    config.after_load_callbacks.append(after_load_callback)

label save_ambient_state:
    # Store current arrangement for manual restoration if needed
    $ current_ambient_arrangement = ambient.active_arrangement.name if ambient.active_arrangement else None

Debug Integration

Development Mode Toggle

init python:
    debug_mode = True
    
    def toggle_ambient_debug():
        if hasattr(store, 'show_ambient_debug'):
            store.show_ambient_debug = not store.show_ambient_debug
        else:
            store.show_ambient_debug = True

# In your developer menu or key binding
label dev_menu:
    menu:
        "Toggle Ambient Debug":
            $ toggle_ambient_debug()
        "Reload Ambient Config":
            $ ambient.load_config()
        "Stop All Ambient":
            $ ambient.stop_ambient(fade_out=False)

Debug Key Binding

init python:
    # Press 'D' to toggle debug
    config.keymap['toggle_debug'] = ['d']
    
    def toggle_debug_action():
        ambient.debug_ui()  # If using CDS debug ui
    
    # Or bind to screen

Performance Optimization

Limiting Active Tracks

init python:
    def optimize_ambient_for_scene(complexity):
        """Adjust ambient based on scene complexity."""
        if complexity == "high":
            # Complex scene - reduce ambient tracks
            ambient.set_base_volume(0.4)
        else:
            ambient.set_base_volume(0.7)

Preloading Audio

init python:
    # Ren'Py handles audio caching, but you can hint
    def preload_location_audio(location):
        # This is handled by Ren'Py's prediction system
        pass

Clone this wiki locally