Skip to guide
StoryomiStoryomiCreator guideOpen Studio
On this page

Examples

Copy-paste patterns for the most common structures. Swap in the character names, asset filenames and variable names you’ve registered in your project.

A full chapter#

Backgrounds, dialogue, choices, variables, conditionals, jumps and endings working together.

chapter_one.omi
## The Message
// Register 'stranger' in Studio → Characters, 'city_night' in Assets → Backgrounds,
// and '$met_stranger' (boolean) in Studio → Variables before running this script.

@bg city_night

// Narration — no speaker, shown as prose overlay
"It was 2 AM when the phone buzzed."

// Dialogue — speaker must be registered in Studio → Characters
player: "Who texts at this hour?"
stranger: "Turn around."

// Choice gate — player picks one branch; the engine waits here
? "What do you do?"
  - "Turn around slowly"
    player: "My hands are shaking."
    stranger: "Smart choice."
    // Session variable assignment — saved with save slots
    $met_stranger = true
    -> The Reveal

  - "Ignore it"
    player: "Probably spam."
    "She put the phone down. Big mistake."
    -> Bad End

## The Reveal
@bg alley_night

"The figure stepped into the light."

// Conditional — branches on a session variable value
? if $met_stranger:
  stranger: "I knew you'd come."
? else:
  stranger: "You shouldn't be here."

stranger: "We need to talk. Not here."

// @end — chapter complete, engine loads the next published chapter
@end

## Bad End
@bg city_night

"Three hours later, she was gone."

// @fin ends the entire story, not just the chapter
@fin

Phone-story syntax tour#

A longer reference for chats, choices, variables and notifications. Try this in a practice phone story with its starter Messages app and Alex conversation.

phone-story-tour.omi
// ═══════════════════════════════════════════════════════════
// Phone-story syntax tour — copy this into a practice chapter.
// Lines starting with // are comments — never shown to players.
//
// Quick orientation:
//   ## Scene Name      → starts a new scene
//   @app <id>          → switches which app is open on screen
//   @chat <chatId>     → opens a chat thread (defined in Apps panel)
//     speaker: "text"  → a chat bubble from that character
//     ~ "text"         → a system message (e.g. "Alex has left")
//   @bg <asset_name>   → sets a background image (upload in Assets)
//   ? reply:           → pauses for the player to choose
//     - "Option A"     → a choice branch
//   @end               → ends this chapter
// ═══════════════════════════════════════════════════════════

## Opening

// ── Step 1: Set the active app ────────────────────────────
// Use @app to open an app on the simulated phone.
// The app ID must match one defined in the Apps panel (left).
// New phone stories include "messages", "social", and "phone".

@app messages

// ── Step 2: Start a chat thread ──────────────────────────
// @chat references a chatId defined inside the "messages" app.
// This example uses the starter conversation with Alex.
// Characters are declared in the Characters panel (left).
// The reserved ID "player" is always available.

@chat chat_alex

  ~ "Monday, 9:14 AM"

  player: "Hey — this is my first Storyomi game."
  player: "Let me show you how choices work."

// ── Step 3: Player choices ────────────────────────────────
// ? pauses the game and shows options to the player.
// Each - branch runs its lines, then continues below.

  ? reply:
    - "Sounds exciting!"
      player: "I love it already."
    - "How does this work?"
      player: "I'll figure it out as I go."

  player: "Both choices lead here — that's linear branching."
  player: "For paths that stay split, use -> to jump to scenes."

## Variables

// ── Step 4: Variables ─────────────────────────────────────
// Declare variables in the Variables panel (left).
// Write to them with $var = value. Read them in conditions.
// Example (uncomment to use — declare 'mood' in the panel first):
//
//   $mood = "curious"
//
//   ? if $mood == "curious":
//     player: "I'm feeling curious today."
//   ? else:
//     player: "I'm not sure how I feel."

@chat chat_alex

  player: "Variables let your story remember choices."
  player: "Declare one in the Variables panel, then use it here."

-> Notifications

## Notifications

// ── Step 5: Notifications ─────────────────────────────────
// @notify sends a system notification banner on the phone.
// Syntax: @notify <appId> "<title>" "<body>"

@notify messages "Messages" "You have a new message"

@app messages

@chat chat_alex

  player: "That was a notification. Tap it to come back here."
  player: "Use @notify any time — even mid-scene."

-> Finish

## Finish

@app messages

@chat chat_alex

  player: "That's the core of OmiScript."
  player: "Delete all of this and start your real story."
  player: "Check the docs at storyomi.com/docs for the full reference."

  ~ "End of demo"

// ── Done ──────────────────────────────────────────────────
// @end closes the chapter. @fin marks the story as complete.

@end

Branching choices#

branching.omi
// Choices can be conditional — an option only appears when its [$condition] is true.
// Conditions read session variables ($var) declared in Studio → Variables.

? "What do you say?"
  - "I believe you" [$trust >= 1]
    // This option only shows if $trust is at least 1
    player: "I believe you."
    sarah: "Thank you. Really."
    $route = "friendship"
    -> Good Path

  - "I don't trust you"
    // No condition — always visible
    player: "I don't trust you."
    sarah: "That's fair. But you're wrong."
    $route = "tense"
    -> Tense Path

  - "Stay quiet" [$met_sarah == false]
    // Only available if the player hasn't met sarah yet
    "You said nothing. Safer that way."
    -> Quiet Path

Messenger scene#

messenger.omi
// Switch to a messenger-type app registered in Studio → Apps.
// All @chat content is deposited into that chat's message log.
@app chatter

// Open a specific chat — chatId must be registered under this app in Studio.
@chat chat_sarah
  // Messages drip in one at a time as the engine runs
  sarah: "hey, you there?"
  sarah: "hello??"

  // Reply gate — the player picks one option to send as their message
  ? reply:
    - "sorry, was asleep"
      player: "sorry just woke up lol"
      sarah: "okay good, we need to talk"
      // Track that the player replied — useful for branching later
      $replied_sarah = true
      -> Replied

    - "leave on read"
      // No player message — sarah gets no response
      -> Left On Read

## Replied
sarah: "meet me at the usual spot. 20 mins."
@end

## Left On Read
// Push a notification into the player's notification shade
@notify chatter "Sarah" "is waiting for your reply..."
@end

Conditional branching#

conditional.omi
// ? if / ? elif / ? else — conditional blocks.
// Evaluates against session ($var) or persistent ($$var) variables.
// Only the first matching branch executes.

? if $route == "friendship":
  sarah: "I'm glad we talked."
  player: "Me too."
? elif $route == "tense":
  sarah: "I hope you change your mind."
  player: "Maybe."
? else:
  // Fallback — runs when no other branch matched
  "They stood in silence."

// Comparison operators: ==  !=  >  <  >=  <=
// Logical: and  or  not
// Example multi-condition: ? if $score >= 10 and $met_sarah:

Phone call (SimOS)#

phone.omi
## The Call
// Switch to a phone-type app registered in Studio → Apps
@app phone

// Incoming call gate — the player picks Answer, Decline, or another branch.
// Dialogue inside a branch becomes the in-call conversation in the Phone app.
? call from sarah:
  - "answer"
    sarah: "Hey. Are you alone?"
    player: "Yeah, why?"
    sarah: "I need you to listen carefully."
    sarah: "Don't come to the office tomorrow."
    -> After Call

  - "decline"
    // No in-call dialogue — goes straight to the branch body
    -> Missed Call

  // 'hang up' is a special branch — runs when the player taps End Call mid-conversation
  - "hang up"
    sarah: "...hello?"
    -> Hung Up

## After Call
"She hung up before you could ask anything."
@end

## Missed Call
// Voicemail — deposited into the phone app's voicemail inbox
@voicemail vm_sarah_001
  from: sarah
  duration: 0:12
  transcript: "It's me. Call me back. Please."
@end

## Hung Up
"You stared at the phone. Had that been a mistake?"
@end

Outgoing phone call (SimOS)#

phone-out.omi
## The Call
@app phone

// Outgoing call gate — the story waits until the player calls Sarah from the Phone app.
// The branches are tried top to bottom; the first whose [condition] is true wins.
? call to sarah:
  hint: "You should call Sarah."
  - "answered" [$relationship_sarah >= 3]
    sarah: "Hey, I was hoping you'd call."
    -> Made Up
  - "answered"
    sarah: "What do you want?"
    -> Cold Call
  - "unanswered"
    // Nobody picks up — no dialogue here
    -> No Answer

## Made Up
@end

## Cold Call
@end

## No Answer
@end

Social feed (SimOS)#

social.omi
## The Post
// Switch to a social_feed-type app registered in Studio → Apps
@app social_feed

// Posts deposit into the feed as the engine runs — displayed newest-first in the UI.
// 'image' is a logical name from Studio → Assets → CGs (no file extension).
@post post_morning
  by: sarah
  text: "Another late night at the office. Someone send help ☕"
  image: sarah_office
  likes: 12
  likeable: true

// Comment gate — the player picks a reply to a specific post.
// appId and postId must reference what's already been deposited above.
? comment on social_feed post_morning:
  - "Hang in there!"
    player: "Hang in there, you've got this!"
    sarah: "Aw, thanks 🥺"
    -> After Comment

  - "Say nothing"
    -> After Comment

## After Comment
// Push a banner notification into the player's notification shade
@notify social_feed "Sarah" "liked your comment"
"Your notification lit up a moment later."
@end

Premium feed with subscription (SimOS)#

premium.omi
## The Exclusive
// Premium feed — requires a premium_feed-type app in Studio → Apps
@app vip_feed

// free: true — always visible regardless of subscription status
@post post_free_001
  by: creator
  text: "Hey everyone, big news coming soon 👀"
  likes: 204
  likeable: true
  free: true

// locked_until — the post is blurred with a lock overlay until the condition is true.
// player_subscribed_to(appId, userId) is a built-in engine expression.
@post post_locked_001
  by: creator
  text: "Here's the full behind-the-scenes video from last night."
  image: bts_video_thumb
  locked_until: player_subscribed_to(vip_feed, creator)
  teaser: "Subscribe to unlock exclusive content 🔒"

// ? subscribe to — narrative-driven subscription gate.
? subscribe to vip_feed creator:
  - "Subscribe"
    creator: "Thank you so much! 🥺"
    // Persistent — survives across playthroughs, so future chapters know
    $$subscribed_vip = true
    -> Subscribed
  - "Maybe later"
    -> Skipped

## Subscribed
"The locked post shimmered and revealed itself."
@end

## Skipped
"The blurred post remained. For now."
@end

Character sprites#

sprites.omi
## The Confrontation
// 'rooftop_night' is a logical name from Studio → Assets → Backgrounds
@bg rooftop_night

// Auto-stage: speaking with () triggers automatic sprite placement.
// The first speaker goes center; the second speaker pushes the first aside.
sarah (): "You followed me up here."
player (): "You left me no choice."

// Named expression — registered in the character's sprite map in Studio.
// Use () for the default expression, (name) for a registered variant.
sarah (angry): "Don't make this harder than it has to be."

// Explicit placement — overrides auto-staging, puts marcus in the left slot.
@sprite left marcus

marcus: "Nobody's going anywhere."

// Clear a slot — removes the sprite visually when a character exits.
@sprite left clear

// sarah and player remain staged in their auto-assigned slots.
sarah: "It's just us now."

// @end — chapter complete. Use @fin to end the entire story.
@end