統合版マイクラには Script API という機能があり、JavaScriptで既存のマイクラのしくみを変えるアドオンを作成ことができます。TypeScriptでの開発にも対応しています。
TypeScriptを使ってのScriptAPIの環境開発構築と
One Block Challenge風のブロックを壊すとランダムに新しいブロックが生成される
しくみ
を作成してみました
用語解説
マイクラには主に統合版とJava版があり
見た目は似ているのですが内部的な機能が全く異なります
紛らわしいので用語を整理します
アドオン
統合版マイクラの機能
世界の見た目を変えるリソースパックresource_pack
と
世界の振る舞いを変えるビヘイビアーパックbehavior_pack
からなる
本記事で取り扱うScript APIもアドオンの機能のひとつ
データパック
Java版マイクラの機能
コマンドやjsonファイルで世界の振る舞いを変えられる
見た目を変えるにはリソースパックと併用する
mod
Java版マイクラに追加する拡張機能のプログラムのこと
Javaなどのプログラミング言語を使って開発をおこなう
Script APIとは全く関係ありません
開発環境
本記事は下記バージョンで動作確認をおこなっています
- windows 11
- 統合版マイクラ 1.21.82
- VScode 1.100.2
- Node.js v22.11.0
これらはインストール済みの状態から、開発環境を整えます
環境構築
プロファイルの作成
コードエディタのVScodeで、統合版マイクラ開発用のプロファイルを作成します
VScodeを起動します
プロファイルの選択
左下の歯車マークから設定を開き、プロファイル->プロファイルを選択します
画像は既に設定済みのためアイコンが変わっています
名前の設定
名前とアイコンをお好みで設定します
チェックボタンをクリックして環境をアクティブにします
mcpeという呼び名は昔統合版マイクラがスマホ向けのポケットエディションPEから開発が始まったことに由来します
なおJava版のデータパック開発環境とはプロファイルを分けておかないと
mcfunctionファイルの文法チェックが競合します
拡張機能導入
統合版マイクラ用のプロファイルを作成してアクティブ化しましたら
VScodeの拡張機能を導入します
画面左側のメニュー、四角を積み重ねているアイコンの拡張機能を表示します
インストール済みの拡張機能一覧
Marketplaceで拡張機能を検索する
の欄に拡張機能名をコピペして検索、インストールしてください
統合版マイクラ用の拡張機能
Bedrock Definitions
統合版マイクラ用オートコンプリート
用語の入力をサポート
VSCode-Bedrock-Development-Extension
統合版マイクラ開発用拡張機能
Minecraft Bedrock Debugger
デバッカー
マイクラと接続してデバッグする方法は拡張機能の説明をご覧ください
便利な拡張機能
Prettier – Code formatter
コードフォーマッター
コードを自動で整えます
Code Spell Checker
英語のスペルミスを指摘します
マイクラ用語もスペルミス判定されますが
クイックフィックス→Add xxx to workspace settingで回避できます
マイクラ用語のスペルミス判定
Add xxx to workspace settingを選択
以上で開発環境の準備は終了です
TypeScriptでマイクラをやってみる
TypeScriptスターターサンプルプロジェクトを利用して、実際にアドオンを作成してマイクラに反映させるまでの手順を確認します
スタータープロジェクトの準備
公式のGitHubリポジトリより、プロジェクトのファイルをダウンロードします
プロジェクトにアクセスします
Code
のプルダウンより、Download ZIP
を選択し、zipファイルをダウンロードします
Download ZIPを選択
ダウンロードしたzipファイルを展開し、ts-starterフォルダを任意の場所にコピーします
ts-starterフォルダを元に開発を進めます
ts-starterフォルダの中身
コピーしたts-starterフォルダをエクスプローラーで表示し
SHIFTを押しながら右クリックでメニューを表示します
codeで開く
を選択します
もし、codeで開くの選択肢が表示されない場合は別の方法で
ts-starterフォルダをVScodeで開いてください
ファイルからフォルダを開く
プロジェクトの復元
ts-starterプロジェクトで利用されているパッケージをインストールします
VScodeで新しいターミナルを表示します
上部メニューより、ターミナル→新しいターミナルを選択します
ターミナルに入力します
パッケージがインストールされます
環境変数の設定
環境変数を設定します
.envファイルを開きます
VScode左側のファイルのアイコンで、開いているフォルダに存在するファイルが一覧表示されます.env
ファイルをクリックして表示します
.envファイル
PROJECT_NAME
はこのアドオンのフォルダ名になりますbehavior_packs
とresource_packs
配下のフォルダ名と一致させる必要があります
フォルダ名は、フォルダ名を右クリック→名前の変更
またはショートカットキーF2で変更できます
他はデフォルトのままにします
manifest.json
マニュフェストファイルを編集します
作成するアドオンの名前や説明などを記載します
behavior_packs
とresource_packs
配下にそれぞれ1ファイルずつあります
behavior_packs\sumiso_sample_ts\manifest.json
{
"format_version": 2,
"header": {
"name": "sumiso_sample_ts",
"description": "TypeScriptでマイクラやってみた",
"uuid": "3a5c17cd-bcc7-4754-b993-47df5a4765d1",
"version": [1, 0, 0],
"min_engine_version": [1, 20, 30]
},
"modules": [
{
"description": "Script resources",
"language": "javascript",
"type": "script",
"uuid": "e8b1d741-eb59-421d-a302-7d6e2ba66030",
"version": [1, 0, 0],
"entry": "scripts/main.js"
}
],
"dependencies": [
{
"module_name": "@minecraft/server",
"version": "1.8.0"
},
{
"uuid": "fa1f7c90-5963-4c10-8bea-069f14aa9c92",
"version": [1, 0, 0]
}
]
}
resource_packs\sumiso_sample_ts\manifest.json
{
"format_version": 2,
"header": {
"name": "sumiso_sample_ts",
"description": "TypeScriptでマイクラやってみた",
"uuid": "fa1f7c90-5963-4c10-8bea-069f14aa9c92",
"version": [1, 0, 0],
"min_engine_version": [1, 20, 30]
},
"modules": [
{
"description": "My Resource Pack",
"type": "resources",
"uuid": "cd721294-e4b6-42a0-afb0-aad512124730",
"version": [1, 0, 0]
}
],
"dependencies": [
{
"uuid": "3a5c17cd-bcc7-4754-b993-47df5a4765d1",
"version": [1, 0, 0]
}
]
}
それぞれheader部分のnameとdescriptionを記載します
header部分の内容がマイクラの画面から確認可能です
headerで設定した内容がマイクラの画面に反映されます
uuidはこのアドオン固有のIDです
適当な生成サイトを利用して、uuidを生成して書き換えます
こちらのサイトでは、更新するたび新しいuuidが生成されます
コピーして、uuidの部分を貼り替えます
headerとmodulesの合計4か所変更します
画面を更新すると新しいuuidが生成されます
dependencies
の部分は
ビヘイビアパック→リソースパックのheaderのuuid
リソースパック→ビヘイビアパックのheaderのuuid
をそれぞれ指定します
依存関係を設定することで
マイクラのワールド設定で、ビヘイビアパックまたはリソースパックを有効化すると
対応するパックも自動的に有効化されるようになります
依存関係の指定をミスってると、対応するパックがないと注意が表示されます(1敗)
main.ts
typescriptのコード本体は、scriptsのmain.tsに書かれています
main.ts
import { world, system } from "@minecraft/server";
function mainTick() {
if (system.currentTick % 100 === 0) {
world.sendMessage("Hello starter! Tick: " + system.currentTick);
}
system.run(mainTick);
}
system.run(mainTick);
- mainTick関数を定義
- system.runで実行
というシンプルなコードが記載されています
mainTick関数の最後に、再度system.run(mainTick)
が呼び出されてます
そのためこの関数は毎tickごとに実行されます
1tickはゲームが処理される最小の時間です
統合版マイクラは20tickで1秒
100tickで5秒になります
したがってmainTickでは
5秒ごとにメッセージを送信する処理になっています
実際にマイクラで動作確認を行います
デプロイ
.envファイルの確認および、manifest.jsonを編集したら
デプロイしてマイクラで利用できるか確認します
VScodeのターミナルでデプロイを実行します
typescriptのコードがjavascriptにコンパイルされ
下記フォルダに配置されます
ビヘイビアーパック
%LOCALAPPDATA%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang\development_behavior_packs
リソースパック
%LOCALAPPDATA%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang\development_resource_packs
統合版マイクラを起動して、テスト用のワールドを作成します
ゲームモードはクリエイティブ
詳細設定で平坦な世界を有効化
オーバーワールドのプリセットを選択します
チートを有効化します
リソースパックに、デプロイしたパックが表示されます
パックを有効化します
ビヘイビアーパックでも有効化されていることを確認します
そのほかの設定はお好みで
ワールドを作成します
typescriptでマイクラに干渉する
5秒ごとにメッセージが表示されることが確認できました
One Block Challenge風ブロック作成
typescriptで統合版マイクラのアドオンが作成できることが確認できました
このファイルを改造してOne Block Challenge風の
壊してもランダムな種類のブロックが無限に再生成されるブロック
を作ってみます
要件定義
One Block Challengeとは次のようなマイクラの配布マップです
よくあるOne Block Challenge
- はじめ1 ブロックだけあり、その上にスポーン
- 1 ブロックを壊すとランダムに新しいブロックが生成される
- 一定数ブロックを壊すと、チェストが出現してアイテムが入手できる
- 一定数ブロックを壊すと、ステージが上がって出現するブロックの種類が変わる
- 最終的にエンドラ討伐できる
もっともコアな機能
1 ブロックを壊すとランダムに新しいブロックが生成される
を実装します
以下この機能をブロック管理と呼びます
詳細設計
ブロック管理機能を分解すると、次の関数に分けられます
- ブロックプールの定義
- ブロック破壊検知
- 無限生成ブロックか判定
- ランダムブロックの選択
- ブロック更新
ブロックプールは、次に生成されるブロックの抽選対象です
マイクラ内にあるすべてのブロックがランダム生成されると
サバイバルで詰んでしまうので
指定したブロックからランダムに生成させます
実装
ブロック破壊検知以外の、
ブロックの管理機能をblock.ts
にまとめました
block.ts
import { Vector3, world, BlockPermutation, Block } from "@minecraft/server";
const overworld = world.getDimension("overworld");
const blockPool: { [stage: number]: { block: string; weight: number }[] } = {
1: [
{ block: "minecraft:grass", weight: 40 },
{ block: "minecraft:dirt", weight: 30 },
{ block: "minecraft:stone", weight: 20 },
{ block: "minecraft:oak_log", weight: 10 },
],
};
function getRandomBlock(stage: number): string {
const pool = blockPool[stage] || blockPool[1];
const totalWeight = pool.reduce((sum, entry) => sum + entry.weight, 0);
let randomValue = Math.random() * totalWeight;
for (const entry of pool) {
if (randomValue entry.weight) {
return entry.block;
}
randomValue -= entry.weight;
}
return "minecraft:dirt";
}
export function isTargetBlock(block: Block) {
const TargetBlockPosition: Vector3 = { x: 0, y: 0, z: 0 };
return (
block.dimension === overworld &&
block.location.x === TargetBlockPosition.x &&
block.location.y === TargetBlockPosition.y &&
block.location.z === TargetBlockPosition.z
);
}
export function updateBlock(block: Block) {
const nextBlockId = getRandomBlock(1);
const blockPermutation = BlockPermutation.resolve(nextBlockId);
block.setPermutation(blockPermutation);
}
typescriptで書けるため、型ヒントをつけて型安全に開発が進められます
だいたいCopilotに生成させたのち、直しています
詳しく見ていきます
ブロックプールの定義
次に生成するブロックの抽選対象です
将来的にstage対応できるような構造になっています
const blockPool: { [stage: number]: { block: string; weight: number }[] } = {
1: [
{ block: "minecraft:grass", weight: 40 },
{ block: "minecraft:dirt", weight: 30 },
{ block: "minecraft:stone", weight: 20 },
{ block: "minecraft:oak_log", weight: 10 },
],
};
ランダムブロックの選択
ブロックプールからランダムにブロックを選択してminecraft:dirt
のようなコマンドで指定する
ブロック名を返します
Copilotに生成させましたが、ルーレット選択になってるらしいです
function getRandomBlock(stage: number): string {
const pool = blockPool[stage] || blockPool[1];
const totalWeight = pool.reduce((sum, entry) => sum + entry.weight, 0);
let randomValue = Math.random() * totalWeight;
for (const entry of pool) {
if (randomValue entry.weight) {
return entry.block;
}
randomValue -= entry.weight;
}
return "minecraft:dirt";
}
無限生成ブロックか判定
ブロック破壊検知では、どこのブロックが壊されてもイベントが発生します
無限再生成するのはOne Blockだけなので、対象の座標にあるか確認します
const overworld = world.getDimension("overworld");
export function isTargetBlock(block: Block) {
const TargetBlockPosition: Vector3 = { x: 0, y: 0, z: 0 };
return (
block.dimension === overworld &&
block.location.x === TargetBlockPosition.x &&
block.location.y === TargetBlockPosition.y &&
block.location.z === TargetBlockPosition.z
);
}
Vector3はマイクラの座標を表現する型です
またオーバーワールド以外のディメンション、たとえばネザーの同じ座標のブロックを壊したときに判定されないように
ディメンションも確認しています
ブロック更新
ブロックを設置するためにはまずBlockPermutation
を取得する必要があります
ブロックの名前と状態の組み合わせです
ブロック破壊検知で取得したBlock
に対して、ブロックの設置を行っています
export function updateBlock(block: Block) {
const nextBlockId = getRandomBlock(1);
const blockPermutation = BlockPermutation.resolve(nextBlockId);
block.setPermutation(blockPermutation);
}
レベルアップしてstageを切り替える機能はないのでstage1固定です
メインの処理、main.tsはシンプルにブロック破壊検知用のイベント登録を行っています
main.ts
import { world, PlayerBreakBlockAfterEvent } from "@minecraft/server";
import { updateBlock, isTargetBlock } from "./block";
function breakBlockEventHandler(e: PlayerBreakBlockAfterEvent) {
if (isTargetBlock(e.block)) {
updateBlock(e.block);
}
}
world.afterEvents.playerBreakBlock.subscribe(breakBlockEventHandler);
型ヒントで何のイベントを登録しているか大変分かりやすい
イベントハンドラーでは
壊したブロックが無限生成の対象ブロックか判定し
対象であればブロックを更新しています
function breakBlockEventHandler(e: PlayerBreakBlockAfterEvent) {
if (isTargetBlock(e.block)) {
updateBlock(e.block);
}
}
イベントにはBeforeEventとAfterEventがあります
ここではブロックが破壊されたあとに再生成したので
AfterEventを利用しています
world.afterEvents.playerBreakBlock.subscribe(breakBlockEventHandler);
BeforeEventだと、イベントのキャンセル
たとえば絶対に壊させないブロックが作成できます
しかし一瞬壊れて再生成されているように見えます
デプロイ
コードを作成したらデプロイします
ホットリロード対応の記載が公式のチュートリアルにはありますが
機能していない様子
毎回コマンドをたたいてデプロイします
動作確認
最初に作ったスーパーフラットのワールドで
座標(0,0,0)にブロックを置き
ブロックを壊すと再生成されるか確認します
もしこの開発中もマイクラのワールドを開きっぱなしで
一時停止にしていたのなら
マイクラ内のコマンドで
を実行してください
デプロイしたアドオンが再読み込みされます
ワールドに入りなおしてもOK
以上で、One Block Challenge風ブロック管理のしくみができました
終わりに
TypeScriptを使ってのScriptAPIの環境開発構築と
One Block Challenge風のブロックを壊すとランダムに新しいブロックが生成される
しくみ
を作成してみました
公式のチュートリアルではテラコッタを壊すゲームcottaを作りながら
- for文を用いたブロックの設置
- スコアボード管理
- モンスターの召喚
などを学べますのでオススメです
Views: 0