1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ScriptAPI] Keystoneとは

1
Last updated at Posted at 2025-12-25

はじめに

ScriptAPIは何かと不便な点が多かったりします。
例えば

  • イベントの処理に優先順位をつけられないから処理が膨れ上がる...
  • Vector3がそもそもただのx,y,zの要素でしかなく、細かな計算などがない...
  • AABBの当たり判定も自前で作らないといけない...
  • Formもレスポンスのデータが数値だったりして条件分けをしないといけない...
  • ifばっかでネストが酷くてコードの可読性が悪すぎる...
  • 遅延処理やリピート処理の扱いがややこしい...

Keystoneを使うことですべて解決します。

比較しようじゃないか

フレンドの申請の承諾処理

Bobさんから申請が来ている前提の処理です。#friend acceptとチャットをすることで承諾をします。その後5秒以内にスニークをした場合承諾を取り消し、しなかった場合確定します。

Keystone未使用
import { ChatSendBeforeEvent, system, world } from '@minecraft/server';

world.beforeEvents.chatSend.subscribe((event: ChatSendBeforeEvent) => {
  if (event.message !== '#friend accept') return;
  event.cancel = true;
  
  const player = event.sender;
  player.sendMessage('「Bob」のフレンド申請を承諾しました。');
  player.sendMessage('キャンセルする場合は、5秒以内にスニークをしてください。');

  let timeout = 5*20;
  const intervalId = system.runInterval(() => {
    if (player.isSneaking) {
      player.sendMessage('フレンド申請の承諾を取り消しました。');
      system.clearRun(intervalId);
      return;
    }
    if (--timeout <= 0) {
      player.sendMessage('フレンド申請の承諾が確定しました。');
      system.clearRun(intervalId);
      return;
    }
  });
});

system.runInterval()の処理が膨れ上がり、if条件分岐が多くて見づらい!

 

Keystone使用
import { EventManager, until } from 'keystonemc';

EventManager.registerBefore('chatSend', {
  handler(event) {
    if (event.message !== '#friend accept') return;
    event.cancel = true;
    
    const player = event.sender;
    player.sendMessage('「Bob」のフレンド申請を承諾しました。');
    player.sendMessage('キャンセルする場合は、5秒以内にスニークをしてください。');

    // 待機
    until({
      when: () => player.isSneaking,
      run: () => player.sendMessage('フレンド申請の承諾を取り消しました。'),
      onTimeout: () => player.sendMessage('フレンド申請の承諾が確定しました。'),
      timeout: 5*20
    });
  },
});

untilですべてを簡潔にわかりやすくまとめられる!


ボタンのタイプ名を送信

プレイヤーが入力したボタンのタイプIDを3秒のカウントダウン後にメッセージで送信します。カウントダウンはアクションバーで送信します。

Keystone未使用
import { Player, ButtonPushAfterEvent, system, world } from '@minecraft/server';

world.afterEvents.buttonPush.subscribe((event: ButtonPushAfterEvent) => {
  const player = event.source;
  if (!(player instanceof Player)) return;

  let i = 3;
  player.onScreenDisplay?.setActionBar(`${i}秒後にボタンのタイプを送信します`);

  const countDown = system.runInterval(() => {
    if (--i <= 0) {
      player.sendMessage(`${event.block.typeId}`);
      system.clearRun(countDown);
      return;
    }

    player.onScreenDisplay?.setActionBar(`${i}秒後にボタンのタイプを送信します`);
  }, 1*20);
});

時系列ではない上、tickIntervalに指定したtick分待ってから処理が始まるので無駄な処理を加える手間がある。

 

Keystone使用
import { Player } from '@minecraft/server';
import { EventManager, sleep } from 'keystonemc';

EventManager.registerAfter('buttonPush', {
  async handler(event) {
    const player = event.source;
    if (!(player instanceof Player)) return;

    for (let i = 3; i > 0; i--) {
      player.onScreenDisplay?.setActionBar(`${i}秒後にボタンのタイプを送信します`);
      await sleep(1*20);
    }

    player.sendMessage(`${event.block.typeId}`);
  }
});

上から下へ流れるように処理が書けるので、メンテナンスもしやすい


ワールドのスポーン地点との距離表示

プレイヤーの位置とワールドのデフォルトのスポーン地点との距離を常にアクションバーで表示します。

Keystone未使用
import { world, system } from '@minecraft/server';

system.runInterval(() => {
  for (const player of world.getPlayers()) {
    if (!player || !player.isValid) continue;

    const playerLocation = player.location;
    const worldSpawnLocation = world.getDefaultSpawnLocation();

    const dx = worldSpawnLocation.x - playerLocation.x;
    const dy = worldSpawnLocation.y - playerLocation.y;
    const dz = worldSpawnLocation.z - playerLocation.z;

    const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);

    player.onScreenDisplay?.setActionBar(`${Math.floor(distance)}m`);
  }
});

まずそもそも計算をする手間がある
※一応 @minecraft/mathVector3Utils::distance(a, b); があります。

 

Keystone使用
import { world } from '@minecraft/server';
import { repeating, Vector3 } from 'keystonemc';

repeating({
  run() {
    for (const player of world.getPlayers()) {
      if (!player || !player.isValid) continue;

      const playerLocation = Vector3.fromBDS(player.location);
      const distance = playerLocation.distance(world.getDefaultSpawnLocation());

      player.onScreenDisplay?.setActionBar(`${Math.floor(distance)}m`);
    }
  }
});

計算する手間なし


初スニーク実行の待機

プレイヤーが参加してから初めてスニークしたのを検知して、その時にメッセージを送ります。

Keystone未使用
import { world, PlayerSpawnAfterEvent, system } from '@minecraft/server';

world.afterEvents.playerSpawn.subscribe((event: PlayerSpawnAfterEvent) => {
  if (!event.initialSpawn) return;
  const player = event.player;

  const checkSneaking = system.runInterval(() => {
    if (player.isSneaking) {
      player.sendMessage('スニーク!');
      system.clearRun(checkSneaking);
    }
  });
});

純粋にコードの量が多い

 

Keystone使用
import { EventManager, waitUntil } from 'keystonemc';

EventManager.registerAfter('playerSpawn', {
  async handler(event) {
    if (!event.initialSpawn) return;
    const player = event.player;

    await waitUntil(() => player.isSneaking);

    player.sendMessage('スニーク!');
  },
});

インターバル処理は一行で終わり


おわり

環境構築はちょいめんどくさいかもですがおすすめです。
特にsleepuntilのおかげで、処理を見やすく書けています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?