はじめに
ScriptAPIは何かと不便な点が多かったりします。
例えば
- イベントの処理に優先順位をつけられないから処理が膨れ上がる...
- Vector3がそもそもただのx,y,zの要素でしかなく、細かな計算などがない...
- AABBの当たり判定も自前で作らないといけない...
- Formもレスポンスのデータが数値だったりして条件分けをしないといけない...
- ifばっかでネストが酷くてコードの可読性が悪すぎる...
- 遅延処理やリピート処理の扱いがややこしい...
Keystoneを使うことですべて解決します。
比較しようじゃないか
フレンドの申請の承諾処理
Bobさんから申請が来ている前提の処理です。#friend acceptとチャットをすることで承諾をします。その後5秒以内にスニークをした場合承諾を取り消し、しなかった場合確定します。
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条件分岐が多くて見づらい!
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秒のカウントダウン後にメッセージで送信します。カウントダウンはアクションバーで送信します。
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分待ってから処理が始まるので無駄な処理を加える手間がある。
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}`);
}
});
上から下へ流れるように処理が書けるので、メンテナンスもしやすい
ワールドのスポーン地点との距離表示
プレイヤーの位置とワールドのデフォルトのスポーン地点との距離を常にアクションバーで表示します。
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/math に Vector3Utils::distance(a, b); があります。
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`);
}
}
});
計算する手間なし
初スニーク実行の待機
プレイヤーが参加してから初めてスニークしたのを検知して、その時にメッセージを送ります。
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);
}
});
});
純粋にコードの量が多い
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('スニーク!');
},
});
インターバル処理は一行で終わり
おわり
環境構築はちょいめんどくさいかもですがおすすめです。
特にsleepやuntilのおかげで、処理を見やすく書けています。