Spaces:
Sleeping
Sleeping
| """Sample Story - Example visual novel story with branching paths.""" | |
| import copy | |
| from typing import List | |
| from engine import ( | |
| VisualNovelBuilder, | |
| SceneState, | |
| CharacterDefinition, | |
| Choice, | |
| background_asset, | |
| sprite_asset, | |
| audio_asset, | |
| create_sprite_data_url, | |
| ) | |
| def build_sample_story() -> List[SceneState]: | |
| """Build the sample story with branching paths.""" | |
| builder = VisualNovelBuilder() | |
| builder.set_characters( | |
| [ | |
| CharacterDefinition( | |
| name="Ari", | |
| image_url=sprite_asset('reachy-mini-cartoon.svg'), | |
| ), | |
| CharacterDefinition( | |
| name="Bo", | |
| image_url=sprite_asset('ReachyMini_emotions_happy.svg'), | |
| animated=True, | |
| ), | |
| ] | |
| ) | |
| builder.set_background( | |
| background_asset('workshop_bg.png'), | |
| ) | |
| builder.set_stage(background_asset("p60-back-cover.png")) | |
| builder.narration("A hush falls over the academy courtyard as the gates creak open.") | |
| builder.set_stage( | |
| background_asset('p3.png'), | |
| ) | |
| # Request player name | |
| builder.request_input("What is your name, traveler?", "player_name") | |
| # After input, create a new state without input_request | |
| state = builder._clone_state() | |
| state.input_request = None # Clear the input request | |
| state.note = "Continuing story" | |
| builder._push_state(state) | |
| builder.show_character("Ari", position="left") | |
| builder.play_sound(audio_asset("wake_up.wav")) | |
| builder.dialogue("Ari", "Welcome, {player_name}! I'm Ari, and this is Bo.") | |
| builder.show_character("Bo", position="right") | |
| builder.dialogue("Bo", "Nice to meet you, {player_name}. We're on a quest to find the star fragment.") | |
| builder.dialogue("Ari", "Will you help us on our quest?") | |
| # ACCEPT BRANCH - tag all scenes with path="accept" | |
| accept_index = len(builder._states) | |
| builder.set_path("accept") | |
| builder.dialogue("Bo", "Excellent! We knew we could count on you, {player_name}!") | |
| builder.move_character("Ari", position="center") | |
| builder.narration("You join Ari and Bo on their adventure...") | |
| # Demonstrate camera feature | |
| builder.set_camera(True) | |
| builder.dialogue("Ari", "First, let me see your face, {player_name}. The camera will help us verify your identity.") | |
| builder.narration("The camera activates, showing your live feed...") | |
| # Demonstrate voice feature | |
| builder.set_camera(False) | |
| builder.set_voice(True) | |
| builder.dialogue("Bo", "Now, tell us about yourself using the voice recorder.") | |
| builder.narration("You can now record or upload audio to interact with the companions.") | |
| # Demonstrate motors feature | |
| builder.set_voice(False) | |
| builder.set_motors(True) | |
| builder.dialogue("Ari", "Finally, we need to test the portal controls. Use the motor panel to align the crystals.") | |
| builder.narration("Motor controls are now available. Adjust the servos to proceed.") | |
| # Example: Send motor commands from the story | |
| builder.send_motor_command(1, 90) # Move motor ID 1 to 90 degrees | |
| builder.dialogue("Ari", "Watch as the first crystal aligns itself!") | |
| # Example: Send multiple motor commands at once | |
| builder.send_motor_commands([(1, 180), (2, 90)]) # Move motors 1 and 2 | |
| builder.dialogue("Ari", "Now the portal crystals are synchronizing!") | |
| # Example: Play sound effect | |
| builder.play_sound(audio_asset("confused1.wav")) # Uncomment when you add audio files | |
| builder.dialogue("Ari", "Listen! The portal resonates with magical energy!") | |
| # Demonstrate robot control (Reachy Mini) | |
| builder.set_motors(False) | |
| builder.set_robot(True) | |
| builder.dialogue("Bo", "Now let's test the Reachy Mini robot! It should be at localhost:8000.") | |
| builder.narration("The robot control panel appears. Make sure your Reachy Mini server is running.") | |
| # Send robot pose command - head looking up and antennas raised | |
| builder.send_robot_pose( | |
| head_x=0.0, | |
| head_y=0.0, | |
| head_z=0.02, # Raise head 2cm | |
| head_pitch=-0.1, # Look up (negative pitch) | |
| antenna_left=-0.2, # Raise left antenna | |
| antenna_right=0.2, # Raise right antenna | |
| ) | |
| builder.dialogue("Ari", "Watch! The robot looks up in wonder!") | |
| # Send another pose - head tilted | |
| builder.send_robot_pose( | |
| head_z=-0.04, # Raise head 2cm | |
| head_roll=0.1, # Tilt head to the side | |
| head_yaw=0.1, # Turn head slightly | |
| antenna_left=-0.3, # Lower left antenna | |
| antenna_right=0.8, # Raise right antenna more | |
| ) | |
| builder.dialogue("Bo", "The robot is expressing curiosity!") | |
| # Demonstrate stage layer with separate blur | |
| builder.set_robot(False) | |
| builder.set_stage(background_asset('p3.png')) # Add a stage layer | |
| builder.dialogue("Ari", "Look! The portal is opening...") | |
| builder.narration("A mystical stage appears between you and the background.") | |
| # Demonstrate separate blur controls | |
| builder.set_background_blur(8) | |
| builder.set_stage_blur(3) | |
| builder.dialogue("Ari", "Wait! Do you sense that? Something magical is happening...") | |
| builder.narration("The background and stage blur independently as Ari steps forward.") | |
| # Clear stage and blur | |
| builder.set_background_blur(0) | |
| builder.set_stage_blur(0) | |
| builder.set_stage("") # Remove stage layer | |
| # Demonstrate character animations and sprite changes | |
| builder.set_character_animation("Bo", "shake") | |
| builder.dialogue("Bo", "Whoa! Did you feel that tremor?!") | |
| builder.set_character_animation("Bo", "bounce") | |
| builder.dialogue("Bo", "This is so exciting! We're getting close!") | |
| builder.set_character_animation("Bo", "") | |
| builder.set_character_animation("Ari", "pulse") | |
| builder.dialogue("Ari", "The star fragment... I can feel its power pulsing nearby.") | |
| # Demonstrate character scaling | |
| builder.set_character_animation("Ari", "") | |
| builder.set_character_scale("Ari", 1.5) | |
| builder.dialogue("Ari", "The power... it's making me grow stronger!") | |
| builder.set_character_scale("Bo", 0.7) | |
| builder.dialogue("Bo", "Whoa, you're getting really big! Or am I shrinking?") | |
| # Reset scales | |
| builder.set_character_scale("Ari", 1.0) | |
| builder.set_character_scale("Bo", 1.0) | |
| # Turn off all features | |
| builder.set_motors(False) | |
| builder.dialogue("Ari", "The portal is ready! But wait...") | |
| builder.dialogue("Bo", "The path splits here! We need to split up to cover more ground.") | |
| builder.dialogue("Ari", "You'll need to choose who to follow, {player_name}.") | |
| # SECOND CHOICE - Follow Ari or Bo | |
| # Remember the index before the branches | |
| follow_ari_index = len(builder._states) | |
| # FOLLOW ARI SUB-BRANCH | |
| builder.set_path("accept.follow_ari") | |
| builder.dialogue("Ari", "Wise choice! My path leads through the ancient library.") | |
| builder.hide_character("Bo") | |
| builder.move_character("Ari", position="center") | |
| builder.set_background(background_asset('p3.png')) | |
| builder.narration("Bo waves goodbye as you follow Ari into the misty corridors...") | |
| builder.dialogue("Ari", "The fragment's energy is strongest here. Stay close!") | |
| builder.set_character_animation("Ari", "pulse") | |
| builder.send_motor_command(1, 45) # Different motor position for this path | |
| builder.dialogue("Ari", "The ancient mechanisms are responding!") | |
| builder.set_character_animation("Ari", "") | |
| builder.narration("You discover the star fragment hidden in an ancient tome.") | |
| builder.dialogue("Ari", "We did it, {player_name}! The knowledge was the key all along.") | |
| builder.play_sound(audio_asset("wake_up.wav")) | |
| builder.narration("✨ Ending: The Scholar's Path (Follow Ari)") | |
| # FOLLOW BO SUB-BRANCH | |
| follow_bo_index = len(builder._states) | |
| builder.set_path("accept.follow_bo") | |
| builder.dialogue("Bo", "Adventure time! My route goes through the crystal caves!") | |
| builder.hide_character("Ari") | |
| builder.move_character("Bo", position="center") | |
| builder.set_background(background_asset('workshop_bg.png')) | |
| builder.narration("Ari nods encouragingly as you follow Bo into the glowing caves...") | |
| builder.dialogue("Bo", "Can you feel the energy? It's electric!") | |
| builder.set_character_animation("Bo", "bounce") | |
| builder.send_motor_commands([(1, 135), (2, 135)]) # Different motor positions | |
| builder.dialogue("Bo", "The crystals are resonating! We're so close!") | |
| builder.set_character_animation("Bo", "shake") | |
| builder.narration("A powerful tremor shakes the cavern as the fragment reveals itself!") | |
| builder.dialogue("Bo", "Whoa! Grab it, {player_name}!") | |
| builder.set_character_animation("Bo", "") | |
| builder.play_sound(audio_asset("wake_up.wav")) | |
| builder.narration("✨ Ending: The Adventurer's Path (Follow Bo)") | |
| # Insert the second choice scene before the sub-branches | |
| second_choice_scene = copy.deepcopy(builder._states[follow_ari_index - 1]) | |
| second_choice_scene.choices = [ | |
| Choice(text="Follow Ari (Library)", next_scene_index=follow_ari_index), | |
| Choice(text="Follow Bo (Caves)", next_scene_index=follow_bo_index), | |
| ] | |
| second_choice_scene.note = "Second Choice (2 paths)" | |
| second_choice_scene.input_request = None | |
| second_choice_scene.path = "accept" # This choice is within the accept path | |
| builder._states[follow_ari_index - 1] = second_choice_scene | |
| # DECLINE BRANCH - tag all scenes with path="decline" | |
| decline_index = len(builder._states) | |
| builder.set_path("decline") | |
| builder.dialogue("Ari", "That's... disappointing, {player_name}.") | |
| builder.hide_character("Bo") | |
| builder.dialogue("Ari", "I guess we're on our own, Bo.") | |
| builder.narration("Ari and Bo leave without you... (Decline path)") | |
| # Insert the choice scene before the branches | |
| choice_scene = copy.deepcopy(builder._states[accept_index - 1]) | |
| choice_scene.choices = [ | |
| Choice(text="Yes, I'll help!", next_scene_index=accept_index), | |
| Choice(text="No, sorry.", next_scene_index=decline_index), | |
| ] | |
| choice_scene.note = "Choice (2 options)" | |
| choice_scene.input_request = None # Make sure no input request on choice scene | |
| choice_scene.path = None # Choice scene is on the main path | |
| builder._states[accept_index - 1] = choice_scene | |
| return builder.build() | |