マイクラと連携してGUIDE01に表示してみた
こんにちは、牧長です。
GUIDE01で何ができるかな~っと考えていたのですが
子供と一緒にマイクラで遊んでいる時にふと思いました
マイクラの情報を補足情報として見れたら便利じゃないか?
技術選定
さて、やりたいことは決まったがどうやって取得したらいいのだろうか
調べてみるとRCONというのが使えそう
RCON(Remote Console)を使うと、サーバの外からコマンドを実行できるらしい
ざっくりと
- 管理者専用の遠隔操作用コンソール
- ゲーム内にログインしていない状態でもサーバコマンドが送れる
- 主に自動化・運用・外部ツール連携に使われる
これを使って、こんな風にしようか

マイクラサーバの立ち上げ
まずはマイクラサーバを立ち上げないといけない
最近は便利ですね
そう、Dockerです
こんな感じのcompose.yamlで一撃で立ち上がりました
services:
# =================================================== #
# Paper Server #
# =================================================== #
paper:
container_name: mc_paper
image: itzg/minecraft-server
tty: true
stdin_open: true
environment:
ENABLE_ROLLING_LOGS: "TRUE"
JVM_OPTS: "-XX:MaxRAMPercentage=75"
TYPE: "PAPER"
EULA: "TRUE"
#VERSION: "LATEST"
MOTD: "The World of Paper Server with Crossplay"
MAX_PLAYERS: 5
ENABLE_COMMAND_BLOCK: "TRUE"
SNOOPER_ENABLED: "FALSE"
VIEW_DISTANCE: 12
PVP: "FALSE"
LEVEL: "lv30066" # a default is "world"
#ONLINE_MODE: "TRUE"
ALLOW_FLIGHT: "TRUE"
USE_NATIVE_TRANSPORT: "TRUE"
STOP_SERVER_ANNOUNCE_DELAY: 60
GUI: "FALSE"
PLUGINS: |
SERVER_PORT: "30066"
# depends_on:
# - velocity
ports:
- "30066:30066/tcp"
- "30066:30066/udp"
- "25575:25575/tcp"
volumes:
- ./paper:/data
- /etc/timezone:/etc/timezone:ro
restart: unless-stopped
あとは下記のようにしたら立ち上がります
$ docker compose up -d
RCONで取得してみる
pythonスクリプトを書いてみます
# app.py
import os, re
from typing import List, Optional
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
from mctools import RCONClient
from dotenv import load_dotenv; load_dotenv(".env")
RCON_HOST = os.getenv("RCON_HOST", "127.0.0.1")
RCON_PORT = int(os.getenv("RCON_PORT", "25575"))
RCON_PASSWORD = os.getenv("RCON_PASSWORD", "your-strong-password")
app = FastAPI(title="Minecraft Status API")
USERNAME_RE = re.compile(r"^[A-Za-z0-9_]{3,16}$")
def ticks_to_hhmm(t: int) -> str:
total = (t + 6000) % 24000
hour = total // 1000
minute = int((total % 1000) * 60 / 1000)
return f"{hour:02d}:{minute:02d}"
def parse_pos(resp: str):
# "Pos: [x d, y d, z d]" or "[x d, y d, z d]"
m = re.search(r"Pos:\s*\[([-\d\.Ee]+)d,\s*([-\d\.Ee]+)d,\s*([-\d\.Ee]+)d\]", resp)
if not m:
m = re.search(r"\[([-\d\.Ee]+)d,\s*([-\d\.Ee]+)d,\s*([-\d\.Ee]+)d\]", resp)
if not m:
raise ValueError(f"座標パース失敗: {resp}")
return tuple(map(float, m.groups()))
def get_online_players(rcon: RCONClient) -> List[str]:
# 1) list uuids(新形式)
resp = rcon.command("list uuids")
if "players online" in resp and ":" in resp:
names = [part.split(" (",1)[0].strip() for part in resp.split(": ",1)[-1].split(",") if part.strip()]
names = [n for n in names if USERNAME_RE.match(n)]
if names:
return names
# 2) フォールバック: list(旧形式)
resp = rcon.command("list")
if "players online" in resp and ":" in resp:
names = [n.strip() for n in resp.split(": ",1)[-1].split(",") if n.strip()]
names = [n for n in names if USERNAME_RE.match(n)]
return names
return []
def query_status(target_player: Optional[str] = None):
rcon = RCONClient(RCON_HOST, port=RCON_PORT, timeout=5.0)
if not rcon.login(RCON_PASSWORD):
raise HTTPException(status_code=503, detail="RCON認証に失敗しました")
try:
# プレイヤー確定
player = (target_player or "").strip()
if player:
if not USERNAME_RE.match(player):
raise HTTPException(status_code=400, detail=f"不正なプレイヤー名: {player}")
else:
online = get_online_players(rcon)
if not online:
raise HTTPException(status_code=404, detail="オンラインのプレイヤーがいません")
player = online[0]
# ここは execute as ではなく data get を直接使う
pos_resp = rcon.command(f"data get entity {player} Pos")
x, y, z = parse_pos(pos_resp)
time_resp = rcon.command("execute in minecraft:overworld run time query daytime")
m = re.search(r"The time is (\d+)", time_resp)
if not m:
raise HTTPException(status_code=502, detail=f"時刻取得に失敗: {time_resp}")
daytime = int(m.group(1))
return {
"player": player,
"position": {"x": x, "y": y, "z": z},
"time": {"daytime_ticks": daytime, "clock_hhmm": ticks_to_hhmm(daytime)},
}
finally:
try: rcon.stop()
except: pass
# ── Schemas
class Position(BaseModel):
x: float; y: float; z: float
class TimeInfo(BaseModel):
daytime_ticks: int; clock_hhmm: str
class StatusResponse(BaseModel):
player: str
position: Position
time: TimeInfo
@app.get("/healthz")
def healthz():
return {"ok": True}
@app.get("/players")
def players():
rcon = RCONClient(RCON_HOST, port=RCON_PORT, timeout=5.0)
if not rcon.login(RCON_PASSWORD):
raise HTTPException(status_code=503, detail="RCON認証に失敗しました")
try:
return {"online": get_online_players(rcon)}
finally:
try: rcon.stop()
except: pass
@app.get("/status", response_model=StatusResponse)
def status(player: Optional[str] = Query(None, description="対象プレイヤー名。未指定ならオンラインから自動選択")):
return query_status(player)
簡単に概要
- FastAPIでHTTPサーバ待ち受け
- RCONでプレイヤーの座標位置とゲーム内時刻を取得
- /statusでjson形式でレスポンス
このスクリプトはvenvを使ってますのでまず初めにこんな感じで仮想環境作っておきましょう
sudo apt install -y python3-venv
python3 -m venv .venv
source .venv/bin/activate
あとはpipでライブラリのインストール
python -m pip install --upgrade pip
pip install mcpi mcrcon fastapi uvicorn mctools dotenv
これで準備ができたので下記のようにコマンドを叩いてサーバを立ち上げましょう
uvicorn app:app --host 0.0.0.0 --port 8080 --env-file .env
これでアクセスすると
{"detail":"オンラインのプレイヤーがいません"}
ゲーム内にログインした状態でアクセスすると
{
"player": "MackyRocky",
"position": {
"x": -8.672398768830408,
"y": 67.0,
"z": 62.533052623416964
},
"time": {
"daytime_ticks": 13288,
"clock_hhmm": "19:17"
}
}
GUIDE01への表示
あとはスマホアプリにGUIDE SDKを組み込んで、上記APIから取得した情報を画面上に表示するとこうなります
最後に
GUIDE01はシンプルなデバイスですが、だからこそアイデア次第で様々なことができるデバイスです
こんなことできたら面白いのでは?みたいなみんなのアイデアで一緒にデバイスを作り上げていきたいです

コメントを送信