※以下の内容の大半はAI(ChatGPT o3-mini-high)によって生成されたものですが、具体的な案や修正、動作チェックにリンクの確認などはすべて私が行っています。万が一にも間違っている説明やコードがあった場合、コメント欄にて。
2025/3/15 – 投稿
2025/3/22 – 文章の修正及び情報の追加
0. 目次
- 1. WebSocketとは何か
- 2. Node.jsのインストール
- 3. Node.jsの動作確認
- 4. WebSocketを利用してHTMLと通信する例
- 5. WebSocketを利用してMinecraftと通信する例
- 6. 主な使い道
- 7. 補足
- 8. おまけ
- 9. 質問について
- 10. 参考になるサイト一覧
1. WebSocketとは何か
WebSocketは、サーバーとクライアント間で常時接続を維持し、双方向・低遅延の通信を実現する通信プロトコルです。
2. Node.jsのインストール
- 公式サイトにアクセス
ブラウザでNode.js公式サイト(日本語版)にアクセスします。
※画面中央付近に「LTS」と表示されたダウンロードボタンがありますので、そちらをクリックします。 - インストーラのダウンロード
Windows版のインストーラ(.msiファイル)がダウンロードされます。
※ファイル名は例として「node-v18.xx.xx-x64.msi」などになっています。 - インストーラの実行
ダウンロードしたファイルをダブルクリックして実行し、以下の手順に従います。
「Next」をクリックライセンス契約に同意する画面で「I Agree(同意する)」をクリックインストール先(通常はデフォルト設定のままで問題ありません)を確認し、「Next」をクリックインストール内容を確認し、「Install」をクリックインストール完了後、「Finish」をクリック - 「Next」をクリック
- ライセンス契約に同意する画面で「I Agree(同意する)」をクリック
- インストール先(通常はデフォルト設定のままで問題ありません)を確認し、「Next」をクリック
- インストール内容を確認し、「Install」をクリック
- インストール完了後、「Finish」をクリック
- 環境変数の確認nインストールが完了すると、Node.jsとnpm(Node Package Manager)は自動的にシステムのPATHに追加されます。
コマンドプロンプトまたはPowerShellを開き、以下のコマンドを入力してバージョンが表示されるか確認してください。
node -v
npm -v
バージョン番号が表示されれば、インストールは正常に完了しています。
【参考情報】
3. Node.jsの動作確認
Node.jsが正しくインストールされているか、簡単なプログラムを実行して確認します。
手順
- プロジェクト用フォルダの作成
任意の場所に新しいフォルダ(例:C:\nodejs_project)を作成します。 - テキストエディタでファイル作成
フォルダ内に「hello.js」というファイルを作成し、以下の内容をコピー&ペーストします。
// hello.js
console.log(“Hello, Node.js!”); - コマンドプロンプトで実行
コマンドプロンプトまたはPowerShellを開き、作成したフォルダに移動します。
(例:cd C:\nodejs_project)
その後、以下のコマンドを実行します。
node hello.js
コンソールに「Hello, Node.js!」と表示されれば、Node.jsが正常に動作しています。
4. WebSocketを利用してHTMLと通信する例
ここでは、HTTPサーバーとしてHTMLファイル(index.html)を配信しながら、WebSocketサーバーでブラウザとリアルタイム通信するサンプルを作成します。
※ディレクトリの設定から動作確認まで、初心者の方でも迷わないように手順を詳しく解説します。
4.1 プロジェクトのセットアップ
- 新規フォルダの作成
例:C:\ws_project というフォルダを作成します。 - コマンドプロンプトでフォルダに移動
cd C:\ws_project - npmプロジェクトの初期化と必要パッケージのインストール
以下のコマンドを順に実行します。
npm init -y
npm install ws
4.2 サーバーサイドのコード(server.js)
- ファイルの作成
C:\ws_project 内に「server.js」というファイルを作成します。 - コードの記述
以下のコードをそのままコピー&ペーストしてください。
// server.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const WebSocket = require("ws");
// プロジェクトルートに配置するindex.htmlファイルのパスを指定
const indexPath = path.join(__dirname, "index.html");
// HTTPサーバーを作成して、index.htmlを返す
const server = http.createServer((req, res) => {
if (req.url === "/" || req.url === "/index.html") {
fs.☆(indexPath, (err, data) => {
if (err) {
res.writeHead(500);
res.end("Error loading index.html");
return;
}
res.writeHead(200, { "Content-Type": "text/html" });
res.end(data);
});
} else {
res.writeHead(404);
res.end("404 Not Found");
}
});
// HTTPサーバーと連携してWebSocketサーバーを起動
const wss = new WebSocket.Server({ server });
wss.on("connection", (ws) => {
console.log("WebSocketクライアントが接続しました。");
ws.on("message", (message) => {
console.log("受信メッセージ:", message);
// クライアントに受信したメッセージをそのまま返す(エコー)
ws.send("エコー: " + message);
});
ws.on("close", () => {
console.log("WebSocketクライアントとの接続が切断されました。");
});
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`HTTP & WebSocketサーバーがポート ${PORT} で起動しました。`);
});
※このサイトのセキュリティ上、4.2 サーバーサイドのコード(server.js)のコード内の「 r e a d F i l e 」(テキストでもエラーが起こるため、空白を入れています)という文字列がエラーを起こしているので、☆記号で置換しています。実際に使用する際は必ず戻してください。
4.3 クライアントサイドのHTMLファイル(index.html)
- ファイルの作成
同じくC:\ws_projectに「index.html」という名前で以下の内容のファイルを作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>WebSocket通信テスト</title>
</head>
<body>
<h1>WebSocket通信テストページ</h1>
<div id="output"></div>
<input type="text" id="messageInput" placeholder="メッセージを入力">
<button id="sendButton">送信</button>
<script>
// 現在のホスト名とポートを利用してWebSocketに接続
const ws = new WebSocket("ws://" + location.host);
ws.onopen = function() {
document.getElementById("output").innerHTML += "<p>WebSocket接続が確立されました。</p>";
};
ws.onmessage = function(event) {
document.getElementById("output").innerHTML += "<p>サーバーから: " + event.data + "</p>";
};
ws.onclose = function() {
document.getElementById("output").innerHTML += "<p>WebSocket接続が切断されました。</p>";
};
// 送信ボタンをクリックしたときの処理
document.getElementById("sendButton").addEventListener("click", function() {
const msg = document.getElementById("messageInput").value;
ws.send(msg);
document.getElementById("messageInput").value = "";
});
</script>
</body>
</html>
4.4 動作確認の手順
- サーバーの起動
コマンドプロンプトでC:\ws_projectに移動し、以下のコマンドを実行します。
node server.js - ブラウザでアクセス
Webブラウザを開いて、
http://localhost:3000
にアクセスしてください。
※index.htmlが表示され、コンソールに「WebSocket接続が確立されました」と表示されれば接続成功です。 - メッセージの送信
テキストボックスにメッセージを入力し「送信」ボタンをクリックすると、サーバー側で受信したメッセージが「エコー」として返され、画面上に表示されます。
5. WebSocketを利用してMinecraftと通信する例
ここでは、Minecraft統合版から送信されるチャットメッセージ(JSON形式、typeが”chat”の場合)のみを対象に、tellコマンドで応答するプログラム例を示します。さらに、Minecraft内からサーバーに接続する際に、
/connect localhost:ポート番号
と入力して接続する方法も説明します。
5.1 プロジェクトのセットアップ
- 新規フォルダの作成
例:C:\minecraft_ws_project というフォルダを作成します。 - コマンドプロンプトでフォルダに移動
cd C:\minecraft_ws_project - npmプロジェクトの初期化と必要パッケージのインストール
以下のコマンドを実行してください
npm init -y
npm install ws uuid
5.2 サーバーサイドのコード(minecraft_ws_server.js)
- ファイルの作成
C:\minecraft_ws_project に「minecraft_ws_server.js」というファイルを作成します。 - コードの記述
以下のコードをコピー&ペーストしてください。
// minecraft_ws_server.js
const WebSocket = require("ws");
const uuid = require("uuid");
// Minecraft用のWebSocketサーバーを19131ポートで起動
const PORT = 19131;
const wss = new WebSocket.Server({ port: PORT });
console.log(`Minecraft WebSocketサーバーがポート ${PORT} で起動しました。`);
wss.on("connection", (ws) => {
console.log("Minecraftクライアントが接続しました。");
// Minecraft側でPlayerMessageイベントを購読するための要求を送信
const subscribeMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "subscribe"
},
body: {
eventName: "PlayerMessage"
}
};
ws.send(JSON.stringify(subscribeMessage));
ws.on("message", (data) => {
let msg;
try {
msg = JSON.parse(data);
} catch (err) {
console.error("JSONパースエラー:", err.message);
return;
}
// typeが"chat"の場合のみ処理する
if (msg.body && msg.body.type === "chat") {
const sender = msg.body.sender;
const text = msg.body.message;
console.log(`【${sender}】からのチャット: ${text}`);
// 応答メッセージを作成
const responseText = `あなたのメッセージ「${text}」を受け取りました!`;
// ダブルクォートのエスケープ処理
const escapedText = responseText.replace(/"/g, "\\"");
// Minecraftに送信するtellrawコマンドを生成
const tellrawCommand = `tellraw ${sender} {"rawtext":[{"text":"${escapedText}"}]}`;
// コマンド発行用メッセージの作成
const commandMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "commandRequest"
},
body: {
origin: { type: "player" },
version: 1,
commandLine: tellrawCommand
}
};
// Minecraftへコマンドを送信
ws.send(JSON.stringify(commandMessage));
console.log(`【${sender}】へtellrawコマンドを送信しました。`);
} else {
// tellやsayなどの他のタイプは無視
console.log("対象外のメッセージを受信しました。");
}
});
ws.on("close", () => {
console.log("Minecraftクライアントとの接続が切断されました。");
});
ws.on("error", (err) => {
console.error("WebSocketエラー:", err.message);
});
});
5.3 Minecraftからの接続と動作確認
- サーバーの起動
コマンドプロンプトでC:\minecraft_ws_projectに移動し、以下のコマンドを実行します。
node minecraft_ws_server.js
コンソールに「Minecraft WebSocketサーバーがポート 19131 で起動しました。」と表示されれば、サーバーの起動は成功です。 - Minecraft内からの接続方法
ワールド設定:
チートが有効になっているワールドを用意してください。
接続コマンド:
ゲーム内のチャット欄に以下のコマンドを入力して、サーバーに接続します。
/connect localhost:19131
※ /connect コマンドは、Minecraft統合版のWebSocket接続を利用するためのコマンドです(環境やプラグインによっては異なる場合があります)。 - チャットメッセージの送信と応答
Minecraft内でチャットにメッセージを入力すると、サーバー側でそのメッセージがJSON形式で受信され、typeが”chat”の場合にtellrawコマンドを生成して、チャットに応答メッセージが表示されます。
コンソールには、受信メッセージや送信状況が詳細にログとして出力されます。
※ホストが接続していれば、他のプレイヤーも使用可能です。
【参考情報】
6. 主な使い道
※以降は私が直接書いています。
MinecraftでWebSocketを利用すると、下記のような機能が作成可能です。
- Node.jsを用いて外部に公開したHTMLを利用して、チャット・設定などを行う、ショップとして使用する(HTTPS+WSS接続を推奨)
- OpenAI APIなどのAIサービスを利用して、Minecraft内でのAIとの対話を実現させる(axiosなどのパッケージを使用)
- Google translate APIなどの翻訳サービスを利用して、Minecraft内でチャットのリアルタイム翻訳を実現させる(axiosなどのパッケージを使用)
- Discordのボットを作成し、Discordとの相互通信を実現させる(discord.jsなどのパッケージを使用)
- 複数のワールドと接続して、ワールド間での相互通信を実現させる
例えば、2つ目に関しては、下記のコードでとりあえずは実装できます。(追加でaxiosのインストールが必要)
const WebSocket = require("ws");
const uuid = require("uuid");
const axios = require("axios");
// ----- 設定 -----
const PORT = 19131; // Minecraft用のWebSocketサーバーのポート番号
const OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"; // ChatGPT APIキー
// ----- WebSocketサーバーの起動 -----
const wss = new WebSocket.Server({ port: PORT });
console.log(`Minecraft WebSocketサーバーがポート ${PORT} で起動しました。`);
wss.on("connection", (ws) => {
console.log("Minecraftクライアントが接続しました。");
// Minecraft側でPlayerMessageイベントを購読するための要求を送信
const subscribeMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "subscribe"
},
body: {
eventName: "PlayerMessage"
}
};
ws.send(JSON.stringify(subscribeMessage));
ws.on("message", async (data) => {
let msg;
try {
msg = JSON.parse(data);
} catch (err) {
console.error("JSONパースエラー:", err.message);
return;
}
// tellやsay以外のチャットメッセージ(typeが"chat")を質問として扱う
if (msg.body && msg.body.type === "chat") {
const sender = msg.body.sender;
const question = msg.body.message;
console.log(`【${sender}】からの質問: ${question}`);
// ChatGPT APIに送信するリクエストボディを作成
const apiRequestBody = {
model: "gpt-4o-mini",// 好きなモデルを使用してください
messages: [
{ role: "user", content: question }
]
};
try {
// ChatGPT APIを呼び出す(タイムアウト10秒)
const response = await axios.post(
"https://api.openai.com/v1/chat/completions",
apiRequestBody,
{
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${OPENAI_API_KEY}`
},
timeout: 10000
}
);
const answer = response.data.choices[0].message.content;
console.log(`【${sender}】への回答: ${answer}`);
// 回答が改行を含む場合は分割して、各行をtellrawコマンドで送信
const lines = answer.split("\n");
lines.forEach((line) => {
const escapedLine = line.replace(/"/g, "\\"");
const tellrawCommand = `tellraw ${sender} {"rawtext":[{"text":"${escapedLine}"}]}`;
const commandMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "commandRequest"
},
body: {
origin: { type: "player" },
version: 1,
commandLine: tellrawCommand
}
};
ws.send(JSON.stringify(commandMessage));
});
} catch (apiErr) {
console.error("ChatGPT APIエラー:", apiErr.message);
// エラー発生時はエラーメッセージをMinecraftへ送信
const errorMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "commandRequest"
},
body: {
origin: { type: "player" },
version: 1,
commandLine: `tellraw ${sender} {"rawtext":[{"text":"API呼び出し中にエラーが発生しました。"}]}`
}
};
ws.send(JSON.stringify(errorMessage));
}
} else {
// tellやsayなどの他のタイプは無視
console.log("対象外のメッセージを受信しました。");
}
});
ws.on("close", () => {
console.log("Minecraftクライアントとの接続が切断されました。");
});
ws.on("error", (err) => {
console.error("WebSocketエラー:", err.message);
});
});
7. 補足
7.1 イベントについて
受信可能なイベントは他にもたくさんあります。
下記のコードを参考に、好きなイベントを使用してください。(そのまま使用すれば、全イベントを受信可能)
const WebSocket = require("ws");
const uuid = require("uuid");
const PORT = 19131;
const wss = new WebSocket.Server({ port: PORT });
console.info(`WebSocket server is running on port ${PORT}.
Connect using "/connect localhost:${PORT}" in Minecraft.`);
// 購読したいイベント名のリスト
const allEvents = [
"AgentCommand",
"AgentCreated",
"AndroidHelpRequest",
"ApiInit",
"AppPaused",
"AppResumed",
"AppSuspended",
"AppUnpaused",
"ArmorStandItemEquipped",
"ArmorStandPosed",
"AssertFailed",
"AvatarsListed",
"AvatarUpdated",
"BehaviorErrored",
"BehaviorFailed",
"BlockBroken",
"BlockBroken",
"BlockChecksumMismatchLevelFailedToLoad",
"BlockFound",
"BlockPlaced",
"BlockPlaced",
"BlockUsageAttempt",
"BlockUsed",
"BookCopied",
"BookEdited",
"BookExported",
"BookImageImported",
"BossKilled",
"BundleSubOfferClicked",
"ButtonPressed",
"CameraUsed",
"CaravanChanged",
"CaravanChanged",
"CauldronUsed",
"ChunkChanged",
"ChunkLoaded",
"ClassroomSettingUpdated",
"ClientIdCreated",
"ClubsEngagement",
"CommandBlockEdited",
"ConfigurationChanged",
"ConnectionFailed",
"ContentShared",
"ControlRemappedByPlayer",
"CraftingSessionCompleted",
"CrashDumpStatus",
"CustomContentRegistered",
"DBStorageError",
"DevConsoleOpen",
"DeviceAccountFailure",
"DeviceAccountSuccess",
"DeviceIdManagerFailOnIdentityGained",
"DeviceLost",
"DifficultySet",
"DiskStatus",
"DwellerDied",
"DwellerRemoved",
"EDUDemoConversion",
"EduOptionSet",
"EmotePlayed",
"GameRulesUpdated",
"GameSessionStart",
"GameTipShown",
"HardwareInfo",
"HowToPlayTopicChanged",
"IDESelected",
"IncognitoFailure",
"InputModeChanged",
"ItemAcquired",
"ItemCrafted",
"ItemDropped",
"ItemEquipped",
"ItemInteracted",
"ItemNamed",
"ItemSmelted",
"ItemTraded",
"JoinCanceled",
"JukeboxUsed",
"LockedItemGiven",
"MobEffectChanged",
"MobInteracted",
"NpcInteracted",
"OfferRated",
"OnAppResume",
"OnAppStart",
"OnAppSuspend",
"OnDeviceLost",
"OptionsUpdated",
"PackHashChanged",
"PackPlayed",
"PackRecovery",
"PackSettings",
"PatternAdded",
"PerformanceContext",
"PerformanceMetrics",
"PermissionsSet",
"PetDied",
"PlayerBanned",
"PlayerBounced",
"PlayerGameModeSet",
"PlayerMessage",
"PlayerSaved",
"PlayerTeleported",
"PlayerTransform",
"PlayerTravelled",
"PopupClosed",
"PopupFiredEdu",
"PortalUsed",
"PortfolioExported",
"PotionBrewed",
"Progressions",
"PurchaseFailedDetailed",
"PushNotificationPermission",
"PushNotificationReceived",
"QueryOfferResult",
"RaidUpdated",
"RealmShared",
"RealmsSubscriptionPurchaseFailed",
"RealmsSubscriptionPurchaseStarted",
"RealmsSubscriptionPurchaseSucceeded",
"RenderingSizeChanged",
"RespondedToAcceptContent",
"ScreenChanged",
"ScreenLoaded",
"ScreenLoadTime",
"ScriptRan",
"ServerConnection",
"ServerConnectionAttempt",
"ServerDrivenLayoutImageLoad",
"ServerDrivenLayoutPageLoad",
"SessionCrashed",
"SignedBookOpened",
"SignIn",
"SignInToEdu",
"SignOut",
"SignOutOfXboxLive",
"SlashCommandExecuted",
"StartClient",
"StartWorld",
"StorageCreated",
"storageReport",
"StoreDiscoveryServiceResponse",
"StoreOfferClicked",
"StorePlayFabResponse",
"StorePromoNotification",
"StorePromoNotificationClicked",
"StoreSearch",
"StoreSessionStartResponse",
"StructureBlockAction",
"TargetBlockHit",
"TextToSpeechToggled",
"TreatmentPackApplied",
"TreatmentPackRemoved",
"Treatments",
"TrialDeviceIdCorrelation",
"UgcDownloadCompleted",
"WaxedOnOrOff",
"WorldGenerated",
"WorldLoadedClassroomCustomization",
"WorldLoadTimes",
"XForgeCatalogSearch"
];
wss.on("connection", (ws) => {
console.log("Client connected");
// 各イベントについて購読要求を送信
allEvents.forEach((eventName) => {
const subscribeMessage = {
header: {
version: 1,
requestId: uuid.v4(),
messageType: "commandRequest",
messagePurpose: "subscribe"
},
body: {
eventName: eventName
}
};
ws.send(JSON.stringify(subscribeMessage));
console.log(`Sent subscription for event: ${eventName}`);
});
// 受信したrawデータをそのまま出力
ws.on("message", (data) => {
const rawData = data.toString();
console.log("Raw data received:", rawData);
});
ws.on("close", () => {
console.log("Client disconnected");
});
ws.on("error", (err) => {
console.error("WebSocket error:", err);
});
});
【参考情報】
7.2 チャットイベントについて
チャットイベント(PlayerMessage)では、通常のチャットを含めた下記の3種類のチャットが受信可能です。
場合分けをしたい場合にうまく活用してください。
- コマンドを使用しない普通のチャット
- sayコマンドを使用したチャット
- tell又はtellrawコマンドを使用したチャット
なお、対象がプレイヤーでなければ受信できず、Script APIなどでチャットを取り消したりしても受信できません。
7.3 座標受信イベントについて
プレイヤーの座標は
- PlayerTransform
- PlayerTravelled
のどちらかのイベントで受信するのがおすすめですが、PlayerTransformイベントは全プレイヤー対応で、PlayerTravelledイベントはホストのみ対応だという違いがあるので注意してください。
なお、どちらのイベントも約1ブロック以上移動しないと受信できず、視点を移動しただけでは受信できないといった点にも注意してください。
7.4 プレイヤーを挟まないデータのやり取りについて
今までの例だとプレイヤーがチャットしてデータを送信したり、チャットメッセージをプレイヤーが直接見てデータを受信していますが、定期的なデータのやり取りを行う場合(特にScript APIとの通信を実現したい場合)、直接データを送る手段がないので、
Minecraft->executeとtellコマンドを使用して、プレイヤーからチャットを送信する->サーバーで受信する->処理してMinecraftに返す
のようにチャットイベントを利用してデータの送信を行うのが現状一番よさそうです。
※ほかのイベントはコマンドやScript APIで起こすのが非常に困難で、渡せるデータもMinecraftのデータがほとんどになってしまう(変更できるのは、エンティティの名前ぐらい)ので、PlayerMessageイベントが一番マシです。
なお、Minecraft側での受信方法は、基本的にはコマンド経由になりますが、
- scoreboardコマンド(数値データを渡したい場合)
- tagコマンド(文字列を渡したい場合)
- scriptevent(Script APIで受信する場合限定)
がおすすめです。(いずれもScript APIで受信可能)
※scripteventはworld.afterEvents.scriptEventReceive.subscribe又はsystem.afterEvents.scriptEventReceiveで受信可能
7.5 外部公開について
作成したサーバー(サーバー自体でも、HTMLだけでも)は、そのまま(localhost)ではそのPC内でしか使用できません。
外部に公開したい場合はポート開放を行う必要があります。
ネットで探せばやり方はいくらでも出てきますが、できない場合は別のツールを使用する必要があります。
いくつか種類はありますが、私のお勧めはSecure Share Netで、無料でも使用可能で有料版なら比較的快適に使用できます。
友人などの安全な人とのみ行う場合はHamachiでもいいかもしれません。
※playit.ggは私が試した感じ危なそうなので、使用は避けた方がよいかと思います。
8. おまけ
結構前に公開したMinecraft内で動画を見れるアドオン生成器を久しぶりに修正したので、配布します。
リンク: Dropbox
※アドオンについて多少の知識がある前提での使用を想定してます。わからない場合は自身で調べてください。
9. 質問について
プログラムのコードに関する質問は、極力自身で調べたりAIに聞いたりしてください。
その他の質問は下記のコメント欄にて。
※このサイトのセキュリティ上、4.2 サーバーサイドのコード(server.js)のコード内の「 r e a d F i l e 」(テキストでもエラーが起こるため、空白を入れています)という文字列がエラーを起こしているので、☆記号で置換しています。
実際に使用する際は必ず戻してください。
コメント