Physical Scoreboard

This tutorial walks you through how you could create a physical scoreboard in your map.

This scoreboard is done using particles with single characters. Those particles are positioned using a marker entity, which is moved relatively.

Project setup

cmcc init "Scores example" "Example demonstrating how to create simple score board"

Marker entity

{
  "format_version": "1.12.0",
  "minecraft:entity": {
    "description": {
      "identifier": "example:marker",
      "is_spawnable": false,
      "is_summonable": true
    },
    "component_groups": {
      "despawn": {
        "minecraft:instant_despawn": {}
      }
    },
    "events": {
      "despawn": {
        "add": {
          "component_groups": [
            "despawn"
          ]
        }
      }
    },
    "components": {
      "minecraft:type_family": {
        "family": [
          "marker"
        ]
      },
      "minecraft:health": {
        "value": 100,
        "max": 100
      },
      "minecraft:fire_immune": true,
      "minecraft:knockback_resistance": {
        "value": 100,
        "max": 100
      },
      "minecraft:damage_sensor": {
        "triggers": {
          "on_damage": {
            "filters": {
              "none_of": [
                {"test": "has_damage", "value": "none"},
                {"test": "has_damage", "value": "override"}
              ]
            }
          },
          "deals_damage": false
        }
      },
      "minecraft:physics": {
        "has_gravity": false
      },
      "minecraft:collision_box": {
        "width": 0,
        "height": 0
      },
      "minecraft:pushable": {
        "is_pushable": false,
        "is_pushable_by_piston": false
      },
      "minecraft:push_through": {
        "value": 1
      }
    }
  }
}

The marker entity is immortal (except void and /kill command) and immovable. In addition, it will quietly despawn, when despawn event has been triggered.

Character particles

All character particles will be almost the same. To avoid copy-pasting them 10 times, we will make use of JSON templating engine.

First we need to define data for the engine. To do this, we edit project.json file and add this field:

  "scope": {
    "particles": [
      {
        "name": "counter_0",
        "texture": "counter/0"
      },
      {
        "name": "counter_1",
        "texture": "counter/1"
      },
      {
        "name": "counter_2",
        "texture": "counter/2"
      },
      {
        "name": "counter_3",
        "texture": "counter/3"
      },
      {
        "name": "counter_4",
        "texture": "counter/4"
      },
      {
        "name": "counter_5",
        "texture": "counter/5"
      },
      {
        "name": "counter_6",
        "texture": "counter/6"
      },
      {
        "name": "counter_7",
        "texture": "counter/7"
      },
      {
        "name": "counter_8",
        "texture": "counter/8"
      },
      {
        "name": "counter_9",
        "texture": "counter/9"
      },
      {
        "name": "counter_dot",
        "texture": "counter/dot"
      },
      {
        "name": "leaderboard",
        "texture": "counter/leaderboard",
        "size": [1.54, 0.16],
        "uv": {
          "texture_width": 77,
          "texture_height": 8,
          "uv": [0, 0],
          "uv_size": [77, 8]
        }
      }
    ]
  }

Next we need to actually create a template particle. All json template names must end with .templ. We create a counter.particle.templ file:

{
  "$files": {
    "array": "particles",
    "fileName": ".particle"
  },
  "$template": {
    "format_version": "1.10.0",
    "particle_effect": {
      "description": {
        "identifier": "example:{{name}}",
        "basic_render_parameters": {
          "material": "particles_alpha",
          "texture": "textures/particles/{{texture}}"
        }
      },
      "components": {
        "minecraft:emitter_rate_instant": {
          "num_particles": 1
        },
        "minecraft:emitter_lifetime_looping": {
          "active_time": 1
        },
        "minecraft:emitter_shape_point": {
          "direction": [-0.5, 0, 0]
        },
        "minecraft:particle_lifetime_expression": {
          "max_lifetime": 1.1
        },
        "minecraft:particle_initial_speed": 1,
        "minecraft:particle_motion_parametric": {
          "relative_position": [0, 0, 0]
        },
        "minecraft:particle_appearance_billboard": {
          "{{?size}}": {
            "size": "{{size}}"
          },
          "{{?!size}}": {
            "size": [0.15, 0.15]
          },
          "facing_camera_mode": "direction_z",
          "{{?uv}}": {
            "uv": "{{uv}}"
          },
          "{{?!uv}}": {
            "uv": {
              "texture_width": 8,
              "texture_height": 8,
              "uv": [0, 0],
              "uv_size": [8, 8]
            }
          }
        },
        "minecraft:particle_appearance_lighting": {}
      }
    }
  }
}

$files object specifies how files will be generated. Our current configuration will generate a separate json file for every element in particles array, and the name of the file will begin with name field of the element. Particle identifiers will also include name field.

In minecraft:particle_appearance_billboard component, {{?size}} means, that if the size field in data is not null, it will add the content to the current object. {{?!size}} is the same predicate, but negated, so content will be added if size field is null.

Code

Now it’s time to get to the code. First we create a file named board.mcc

func displayScore(marker:context , n:int , score:int , denominator:int) {
    // Show place number
    showChar(marker, n);
    for (marker) {
        >>tp @s ~~~0.25
    }
    // Show place dot
    showChar(marker, 10);
    for (marker) {
        >>tp @s ~~~0.3
    }
    // Show score
    do {
        let digit = score / denominator;
        score = score % denominator;
        denominator = denominator / 10;
        for (marker) {
            >>tp @s ~~~0.25
        }
        showChar(marker, digit);
    } while (denominator > 0);
}

// Simple function, that shows character particle at marker location
func showChar(marker:context , digit:int) {
    switch (digit) {
        case 0 {
            for (marker) {
                >>particle example:counter_0 ~~~
            }
        }
        case 1 {
            for (marker) {
                >>particle example:counter_1 ~~~
            }
        }
        case 2 {
            for (marker) {
                >>particle example:counter_2 ~~~
            }
        }
        case 3 {
            for (marker) {
                >>particle example:counter_3 ~~~
            }
        }
        case 4 {
            for (marker) {
                >>particle example:counter_4 ~~~
            }
        }
        case 5 {
            for (marker) {
                >>particle example:counter_5 ~~~
            }
        }
        case 6 {
            for (marker) {
                >>particle example:counter_6 ~~~
            }
        }
        case 7 {
            for (marker) {
                >>particle example:counter_7 ~~~
            }
        }
        case 8 {
            for (marker) {
                >>particle example:counter_8 ~~~
            }
        }
        case 9 {
            for (marker) {
                >>particle example:counter_9 ~~~
            }
        }
        case 10 {
            for (marker) {
                >>particle example:counter_dot ~~~
            }
        }
    }
}

After that we edit the main.mcc file

import "board.mcc"

define timer;
define s1;
define s2;
define s3;
define s4;
define s5;

func start() {
    debug("RAM reset");
    // init scores
    s1 = 19876;
    s2 = 777;
    s3 = 20;
    s4 = 1;
    s5 = 0;
}

func update() {
    // Increment timer every tick and if 20 ticks has passed, we display score and reset the timer
    timer++;
    if (timer >= 20) {
        // Coordinates were adjusted so it looks like the particles are attached to the wall
        >>particle example:leaderboard 180.90 72.5 141.5
        >>event entity @e[type=example:marker] despawn
        >>summon example:marker 180.95 72.0 139.0
        context marker = @e[type=example:marker];
        displayScore(marker, 1, s1, 10000);
        // Every time we draw another score, we reset x and z marker position and move relatively in y axis
        for (marker) {
            >>tp @s 180.95 ~-0.4 139.0
        }
        displayScore(marker, 2, s2, 10000);
        for (marker) {
            >>tp @s 180.95 ~-0.4 139.0
        }
        displayScore(marker, 3, s3, 10000);
        for (marker) {
            >>tp @s 180.95 ~-0.4 139.0
        }
        displayScore(marker, 4, s4, 10000);
        for (marker) {
            >>tp @s 180.95 ~-0.4 139.0
        }
        displayScore(marker, 5, s5, 10000);
        // Clean up marker after drawing score
        >>event entity @e[type=example:marker] despawn
        timer = 0;
    }
}

World setup

Execute the cmcc install command.

Add both behavior and resource pack to the world. Place a repeating command block and set its command to function main and Redstone to Always active.

For aesthetics, add a wall of black wool behind the scores.

Finished example: Download