Skip to content

Tutorial: Parkour Race

Build a timed parkour course with checkpoints and records.

What You'll Learn

  • Timer system using scoreboards
  • Checkpoint system
  • Personal best tracking
  • Fall detection and respawn

Final Result

Players race through a parkour course, checkpoints save progress, and best times are recorded.

Step 1: Setup

mcrs
import "stdlib/effects.mcrs"
import "stdlib/particles.mcrs"

const START_X: int = 0;
const START_Y: int = 64;
const START_Z: int = 0;
const FALL_Y: int = 50;  // Below this = fell
const CHECKPOINT_COUNT: int = 5;

Step 2: Scoreboards

mcrs
@load
fn init() {
    // Time tracking (in ticks, 20 = 1 second)
    scoreboard_add_objective("pk_time", "dummy");
    
    // Personal best
    scoreboard_add_objective("pk_best", "dummy");
    
    // Current checkpoint (0-5)
    scoreboard_add_objective("pk_checkpoint", "dummy");
    
    // Is player racing? (0/1)
    scoreboard_add_objective("pk_running", "dummy");
    
    // Display leaderboard
    scoreboard_display("sidebar", "pk_best");
    
    set_day();
    weather_clear();
    
    announce("§b[Parkour] §fStep on pressure plate to start!");
}

Step 3: Start Race

mcrs
fn start_race(player: selector) {
    // Prevent double-start
    let running: int = scoreboard_get(player, "pk_running");
    if (running == 1) {
        tell(player, "§cAlready racing!");
        return;
    }
    
    // Initialize
    scoreboard_set(player, "pk_running", 1);
    scoreboard_set(player, "pk_time", 0);
    scoreboard_set(player, "pk_checkpoint", 0);
    
    // Teleport to start
    tp(player, START_X, START_Y, START_Z);
    
    // Clear any effects
    effect_clear(player);
    
    title(player, "§bGO!");
    
    sparkles(player);
}

Step 4: Timer Loop

mcrs
@tick
fn race_tick() {
    foreach (p in @a) {
        let running: int = scoreboard_get(p, "pk_running");
        
        if (running == 1) {
            // Increment time
            scoreboard_add(p, "pk_time", 1);
            
            // Display current time
            let time: int = scoreboard_get(p, "pk_time");
            let seconds: int = time / 20;
            let ms: int = (time % 20) * 5;
            actionbar(p, "§e⏱ " + seconds + "." + ms + "s");
            
            // Check for fall
            check_fall(p);
            
            // Check checkpoints
            check_checkpoints(p);
        }
    }
}

Step 5: Fall Detection

mcrs
fn check_fall(player: selector) {
    // If player falls below FALL_Y
    execute if entity player[y=..50] run {
        let cp: int = scoreboard_get(player, "pk_checkpoint");
        
        // Respawn at checkpoint
        respawn_at_checkpoint(player, cp);
        
        tell(player, "§cFell! Back to checkpoint " + cp);
        angry(player);
    }
}

fn respawn_at_checkpoint(player: selector, cp: int) {
    // Checkpoint coordinates
    if (cp == 0) {
        tp(player, 0, 64, 0);
    }
    if (cp == 1) {
        tp(player, 0, 64, 50);
    }
    if (cp == 2) {
        tp(player, 0, 70, 100);
    }
    if (cp == 3) {
        tp(player, 0, 75, 150);
    }
    if (cp == 4) {
        tp(player, 0, 80, 200);
    }
}

Step 6: Checkpoint System

mcrs
fn check_checkpoints(player: selector) {
    let current_cp: int = scoreboard_get(player, "pk_checkpoint");
    
    // Only check next checkpoint (prevents skipping)
    
    // Checkpoint 1 at (0, 64, 50)
    if (current_cp == 0) {
        execute if entity player[x=-2..2, y=62..68, z=48..52] run {
            reach_checkpoint(player, 1);
        }
    }
    
    // Checkpoint 2 at (0, 70, 100)
    if (current_cp == 1) {
        execute if entity player[x=-2..2, y=68..74, z=98..102] run {
            reach_checkpoint(player, 2);
        }
    }
    
    // Checkpoint 3 at (0, 75, 150)
    if (current_cp == 2) {
        execute if entity player[x=-2..2, y=73..79, z=148..152] run {
            reach_checkpoint(player, 3);
        }
    }
    
    // Checkpoint 4 at (0, 80, 200)
    if (current_cp == 3) {
        execute if entity player[x=-2..2, y=78..84, z=198..202] run {
            reach_checkpoint(player, 4);
        }
    }
    
    // Finish at (0, 64, 250)
    if (current_cp == 4) {
        execute if entity player[x=-2..2, y=62..68, z=248..252] run {
            finish_race(player);
        }
    }
}

fn reach_checkpoint(player: selector, cp: int) {
    scoreboard_set(player, "pk_checkpoint", cp);
    
    subtitle(player, "§aCheckpoint " + cp + "/" + CHECKPOINT_COUNT);
    
    happy(player);
    playsound("minecraft:entity.experience_orb.pickup", "player", player);
}

Step 7: Finish Race

mcrs
fn finish_race(player: selector) {
    // Stop the race
    scoreboard_set(player, "pk_running", 0);
    
    let time: int = scoreboard_get(player, "pk_time");
    let best: int = scoreboard_get(player, "pk_best");
    let seconds: int = time / 20;
    let ms: int = (time % 20) * 5;
    
    // Check for new record
    if (best == 0) {
        // First completion
        scoreboard_set(player, "pk_best", time);
        title(player, "§6Finished!");
        subtitle(player, "§e" + seconds + "." + ms + "s §7(First Record)");
    } else {
        if (time < best) {
            // New personal best!
            scoreboard_set(player, "pk_best", time);
            title(player, "§6New Record!");
            subtitle(player, "§e" + seconds + "." + ms + "s");
            announce("§b[Parkour] §6New Record! §e" + seconds + "." + ms + "s");
        } else {
            title(player, "§aFinished!");
            subtitle(player, "§e" + seconds + "." + ms + "s");
        }
    }
    
    sparkles(player);
    playsound("minecraft:ui.toast.challenge_complete", "player", player);
}

Step 8: Quit Race

mcrs
fn quit_race(player: selector) {
    let running: int = scoreboard_get(player, "pk_running");
    
    if (running == 1) {
        scoreboard_set(player, "pk_running", 0);
        tell(player, "§cRace abandoned");
        tp(player, START_X, START_Y, START_Z);
    }
}

Complete Code

See the full example: parkour_race.mcrs

Enhancements

  • Add global leaderboard (top 10 times)
  • Add ghost replay system
  • Add multiple courses with difficulty levels
  • Add power-ups (speed boost, double jump)
  • Add time penalties for falling

Released under the MIT License.