Skip to content

Cutscene System

Cutscenes are scripted cinematic sequences that take control of the player’s camera and play a series of keyframed actions — camera movements, NPC animations, dialogue, sound effects, particle effects, and more. They’re powered by Ceremony’s cutscene engine under the hood.


Here’s a simple cutscene that pans the camera to a location and shows dialogue:

File: config/journey/cutscenes/professor_intro.json

{
"id": "professor_intro",
"duration": 8000,
"keyframes": [
{
"tick": 0,
"type": "camera",
"subtype": "move_to",
"data": {
"x": 100.5,
"y": 72.0,
"z": 200.5,
"yaw": 180.0,
"pitch": -10.0,
"duration": 2000,
"easing": "ease_in_out"
}
},
{
"tick": 2000,
"type": "dialogue",
"subtype": "show",
"data": {
"speaker": "Professor Willow",
"text": "<gold>Welcome to the world of Pokemon!",
"duration": 3000
}
},
{
"tick": 5000,
"type": "dialogue",
"subtype": "show",
"data": {
"speaker": "Professor Willow",
"text": "<gray>Your adventure begins here. Choose wisely!",
"duration": 2500
}
}
],
"onComplete": "q.player.add_flag('intro_cutscene_seen');",
"onCancel": ""
}

FieldTypeRequiredDescription
idStringYesUnique identifier for the cutscene
durationNumberYesTotal duration in milliseconds
keyframesArrayYesArray of keyframe actions (see below)
onCompleteStringNoMoLang script to run when the cutscene finishes normally
onCancelStringNoMoLang script to run if the player cancels the cutscene

Each keyframe fires an action at a specific moment during the cutscene:

{
"tick": 0,
"type": "camera",
"subtype": "move_to",
"data": { }
}
FieldTypeDescription
tickNumberWhen this action fires (milliseconds from cutscene start)
typeStringAction category (see action types below)
subtypeStringSpecific action within the category
dataObjectAction-specific parameters

Control the player’s camera position and rotation.

SubtypeDescription
move_toSmoothly move the camera to a position
look_atRotate camera to look at coordinates
look_at_entityRotate camera to track an entity
shakeApply camera shake effect
zoomChange field of view
resetReturn camera to player
set_positionInstantly set camera position (no interpolation)

move_to:

{
"tick": 0,
"type": "camera",
"subtype": "move_to",
"data": {
"x": 100.5, "y": 72.0, "z": 200.5,
"yaw": 180.0, "pitch": -10.0,
"duration": 2000,
"easing": "ease_in_out"
}
}

look_at:

{
"tick": 2000,
"type": "camera",
"subtype": "look_at",
"data": {
"x": 105.0, "y": 70.0, "z": 195.0,
"duration": 1000,
"easing": "ease_out"
}
}

look_at_entity:

{
"tick": 2000,
"type": "camera",
"subtype": "look_at_entity",
"data": {
"entity_uuid": "npc-uuid-here",
"duration": 1000,
"easing": "ease_out"
}
}

shake:

{
"tick": 5000,
"type": "camera",
"subtype": "shake",
"data": {
"intensity": 0.5,
"duration": 1000,
"frequency": 10.0
}
}

zoom:

{
"tick": 3000,
"type": "camera",
"subtype": "zoom",
"data": {
"fov": 30.0,
"duration": 500,
"easing": "ease_in"
}
}

Control NPCs and entities during the cutscene.

SubtypeDescription
spawnSpawn an entity at a position
despawnRemove an entity
move_toMove entity to a position
look_atMake entity face a position
animatePlay an entity animation
set_poseSet entity body pose
set_nameSet entity display name

spawn:

{
"tick": 0,
"type": "entity",
"subtype": "spawn",
"data": {
"entity_type": "cobblemon:npc",
"x": 100.0, "y": 70.0, "z": 200.0,
"yaw": 180.0,
"tag": "intro_professor"
}
}

move_to:

{
"tick": 2000,
"type": "entity",
"subtype": "move_to",
"data": {
"tag": "intro_professor",
"x": 105.0, "y": 70.0, "z": 200.0,
"speed": 0.5,
"duration": 2000
}
}

animate:

{
"tick": 4000,
"type": "entity",
"subtype": "animate",
"data": {
"tag": "intro_professor",
"animation": "wave"
}
}

Modify blocks in the world during the cutscene.

SubtypeDescription
setChange a block at a position
fillFill an area with blocks
restoreRestore blocks to their original state
interactSimulate block interaction (doors, levers)

set:

{
"tick": 3000,
"type": "block",
"subtype": "set",
"data": {
"x": 100, "y": 70, "z": 200,
"block": "minecraft:redstone_lamp[lit=true]"
}
}

Particles, screen effects, and visual overlays.

SubtypeDescription
particleSpawn particles at a position
screen_fadeFade screen to/from black
screen_tintApply a color tint overlay
titleShow title text
vignetteApply vignette effect

particle:

{
"tick": 5000,
"type": "effect",
"subtype": "particle",
"data": {
"particle": "minecraft:end_rod",
"x": 100.0, "y": 72.0, "z": 200.0,
"count": 20,
"spread": 1.0
}
}

screen_fade:

{
"tick": 0,
"type": "effect",
"subtype": "screen_fade",
"data": {
"from_alpha": 1.0,
"to_alpha": 0.0,
"duration": 1000,
"color": "#000000"
}
}

title:

{
"tick": 2000,
"type": "effect",
"subtype": "title",
"data": {
"title": "<gold>Chapter 1",
"subtitle": "<gray>A New Beginning",
"fade_in": 500,
"stay": 2000,
"fade_out": 500
}
}

Play sounds and music.

{
"tick": 0,
"type": "sound",
"subtype": "play",
"data": {
"sound": "minecraft:music_disc.otherside",
"volume": 0.8,
"pitch": 1.0,
"x": 100.0, "y": 70.0, "z": 200.0
}
}

To stop a sound:

{
"tick": 8000,
"type": "sound",
"subtype": "stop",
"data": {
"sound": "minecraft:music_disc.otherside"
}
}

Show dialogue boxes with speaker names.

{
"tick": 2000,
"type": "dialogue",
"subtype": "show",
"data": {
"speaker": "Professor Willow",
"text": "<white>Your journey begins today!",
"duration": 3000
}
}

To clear dialogue:

{
"tick": 5000,
"type": "dialogue",
"subtype": "clear",
"data": {}
}
SubtypeDescription
commandRun a server command
scriptRun a MoLang script

command:

{
"tick": 6000,
"type": "special",
"subtype": "command",
"data": {
"command": "give {player} cobblemon:poke_ball 5"
}
}

script:

{
"tick": 7000,
"type": "special",
"subtype": "script",
"data": {
"script": "q.player.add_flag('intro_seen'); q.player.start_task('journey:first_quest');"
}
}

Camera and entity movements support easing for smooth transitions:

EasingDescription
linearConstant speed
ease_inStart slow, end fast
ease_outStart fast, end slow
ease_in_outSlow start and end, fast middle

When a cutscene starts, Journey automatically:

  1. Saves the player’s current position, rotation, gamemode, and HUD state
  2. Locks player movement and input
  3. Hides the HUD (health, hunger, hotbar)
  4. Disables interaction with the world

When the cutscene ends (or is cancelled), all state is restored — the player returns to exactly where they were with the same gamemode and HUD settings.


Players can cancel cutscenes by pressing a configured key (default: Escape). When cancelled:

  1. The onCancel script runs (if defined)
  2. Player state is restored
  3. Any spawned cutscene entities are cleaned up
  4. Block changes are reverted

If onCancel is empty, cancellation simply restores the player. Use onCancel to handle cleanup that differs from normal completion:

{
"onComplete": "q.player.add_flag('watched_intro');",
"onCancel": "q.player.tell_minimessage('<yellow>You can replay this cutscene later.');"
}

Play and control cutscenes from any MoLang context:

FunctionReturnsDescription
q.player.play_cutscene('cutscene_id')1.0Start a cutscene for the player
q.player.stop_cutscene()1.0Stop the current cutscene
q.player.in_cutscene()1.0 / 0.0Check if the player is in a cutscene
q.player.cutscene_progress()0.0 - 1.0Get current cutscene progress
{
"type": "script",
"data": {
"scripts": [
"q.player.play_cutscene('gym_victory_cinematic');"
]
}
}

Example: Condition Based on Cutscene State

Section titled “Example: Condition Based on Cutscene State”
{
"filter": "!q.player.in_cutscene()"
}

A cutscene that pans to the gym leader, shows dialogue, then returns control to the player.

File: config/journey/cutscenes/brock_intro.json

{
"id": "brock_intro",
"duration": 12000,
"keyframes": [
{
"tick": 0,
"type": "effect",
"subtype": "screen_fade",
"data": {
"from_alpha": 0.0,
"to_alpha": 1.0,
"duration": 500,
"color": "#000000"
}
},
{
"tick": 500,
"type": "camera",
"subtype": "set_position",
"data": {
"x": 500.5, "y": 75.0, "z": -200.5,
"yaw": 0.0, "pitch": -15.0
}
},
{
"tick": 500,
"type": "effect",
"subtype": "screen_fade",
"data": {
"from_alpha": 1.0,
"to_alpha": 0.0,
"duration": 1000,
"color": "#000000"
}
},
{
"tick": 1500,
"type": "camera",
"subtype": "move_to",
"data": {
"x": 500.5, "y": 72.0, "z": -195.0,
"yaw": 0.0, "pitch": -5.0,
"duration": 2000,
"easing": "ease_in_out"
}
},
{
"tick": 3500,
"type": "sound",
"subtype": "play",
"data": {
"sound": "minecraft:block.note_block.pling",
"volume": 0.8,
"pitch": 1.2
}
},
{
"tick": 4000,
"type": "dialogue",
"subtype": "show",
"data": {
"speaker": "Brock",
"text": "<gray>So, you've made it this far...",
"duration": 3000
}
},
{
"tick": 7000,
"type": "dialogue",
"subtype": "show",
"data": {
"speaker": "Brock",
"text": "<gold>I am Brock, the Pewter City Gym Leader!",
"duration": 3000
}
},
{
"tick": 10000,
"type": "effect",
"subtype": "screen_fade",
"data": {
"from_alpha": 0.0,
"to_alpha": 1.0,
"duration": 1000,
"color": "#000000"
}
},
{
"tick": 11000,
"type": "camera",
"subtype": "reset",
"data": {}
},
{
"tick": 11000,
"type": "effect",
"subtype": "screen_fade",
"data": {
"from_alpha": 1.0,
"to_alpha": 0.0,
"duration": 1000,
"color": "#000000"
}
}
],
"onComplete": "q.player.add_flag('met_brock');",
"onCancel": ""
}

Cutscene files go in config/journey/cutscenes/:

config/journey/cutscenes/
├── professor_intro.json
├── brock_intro.json
├── gym_victory.json
└── story/
├── chapter1_opening.json
└── chapter1_ending.json

The cutscene ID matches the filename (without extension). Use this ID in q.player.play_cutscene().


  • Fade to black between camera position changes for smooth transitions
  • Keep dialogue on screen for at least 2-3 seconds so players can read
  • Test timing in-game — what feels right in JSON may need adjustment
  • Use onCancel to handle players who skip the cutscene
  • Layer sound with camera movements for immersive effect
  • Start simple — a camera pan + dialogue is a great first cutscene
  • For quick dialogue sequences without camera control, use Timelines instead