いいね、その流れかなりきれいにOS化できそう。雛型レベルでまとめてみるね。


全体像

  • Roblox側: HttpService:GetAsync() などで PNG を取得
  • Python + Flask: REST API で PNG を返す
  • MySQL: 惑星メタ情報+PNGの場所(URL or BLOB)を保持

ここでは:

  • 惑星ごとに「地形PNG」を1枚持つシンプル版
  • 必要に応じて「タイル分割」などに拡張できる前提

で設計する。


MySQL スキーマ案

CREATE TABLE planets (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    code VARCHAR(64) NOT NULL UNIQUE, -- APIで使う識別子 (例: "terra_001")
    description TEXT,
    png_path VARCHAR(512) NOT NULL,   -- サーバ内パス or ストレージURL
    width INT NOT NULL,               -- 画像幅(px)
    height INT NOT NULL,              -- 画像高さ(px)
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • code を Roblox から指定するIDとして使うと扱いやすい。
  • png_path
    • ローカルパス(例: /data/planets/terra_001.png
    • もしくは外部ストレージURL のどちらでもOK。

URL設計案

RESTっぽく、かつ Roblox から叩きやすい形にする:

  • 惑星一覧(メタ情報のみ)
    • GET /api/planets
  • 惑星詳細(メタ情報)
    • GET /api/planets/<code>
  • 惑星の地形PNG本体
    • GET /api/planets/<code>/heightmap.png

Roblox側は主に最後のエンドポイントを叩いて PNG を取得する想定。


Flask 雛型コード

from flask import Flask, jsonify, send_file, abort
import mysql.connector
import os

app = Flask(__name__)

# --- DB接続設定(環境変数などで管理するのが本番向き) ---
DB_CONFIG = {
    'host': 'localhost',
    'user': 'planet_user',
    'password': 'planet_pass',
    'database': 'planet_db',
    'port': 3306,
}

def get_db_connection():
    return mysql.connector.connect(**DB_CONFIG)

# --- 惑星一覧 ---
@app.route('/api/planets', methods=['GET'])
def list_planets():
    conn = get_db_connection()
    cur = conn.cursor(dictionary=True)
    cur.execute("""
        SELECT id, name, code, description, width, height, created_at
        FROM planets
        ORDER BY id ASC
    """)
    rows = cur.fetchall()
    cur.close()
    conn.close()
    return jsonify(rows)

# --- 惑星詳細 ---
@app.route('/api/planets/<code>', methods=['GET'])
def get_planet(code):
    conn = get_db_connection()
    cur = conn.cursor(dictionary=True)
    cur.execute("""
        SELECT id, name, code, description, width, height, png_path, created_at
        FROM planets
        WHERE code = %s
        LIMIT 1
    """, (code,))
    row = cur.fetchone()
    cur.close()
    conn.close()

    if not row:
        abort(404, description="Planet not found")

    # png_path は内部情報なので返さない or 別フィールド名で返す
    row.pop('png_path', None)
    return jsonify(row)

# --- 惑星の地形PNGを返す ---
@app.route('/api/planets/<code>/heightmap.png', methods=['GET'])
def get_planet_heightmap(code):
    conn = get_db_connection()
    cur = conn.cursor(dictionary=True)
    cur.execute("""
        SELECT png_path
        FROM planets
        WHERE code = %s
        LIMIT 1
    """, (code,))
    row = cur.fetchone()
    cur.close()
    conn.close()

    if not row:
        abort(404, description="Planet not found")

    png_path = row['png_path']

    if not os.path.exists(png_path):
        abort(404, description="PNG file not found")

    # Roblox HttpService は基本バイナリOKなので image/png で返す
    return send_file(png_path, mimetype='image/png')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Roblox 側からの呼び出しイメージ

local HttpService = game:GetService("HttpService")

local baseUrl = "http://your-flask-server:5000"

local planetCode = "terra_001"
local url = baseUrl .. "/api/planets/" .. planetCode .. "/heightmap.png"

-- ここは実際には ImageLabel.Image にURLを指定するか、
-- 一旦ファイルとして扱うかなど、運用に応じて変わる。
local result = HttpService:GetAsync(url)
-- result はバイナリ文字列なので、そのままでは使いづらい。
-- Robloxでの扱い方は、用途(テクスチャ化 or 外部ホスティング)次第。

(Roblox内で直接PNGバイナリをテクスチャにするのは制約があるので、
CDNに置いてURLをImageに指定する方式も検討対象になる。)


拡張の余地(地形万華鏡っぽくするなら)

  • タイル分割:
    • planet_tiles テーブルを追加して
      • planet_id
      • tile_x, tile_y
      • png_path
    • URL:
      • GET /api/planets/<code>/tiles/<int:x>/<int:y>.png
  • バリエーション(万華鏡)
    • variant カラムを持たせて
      • GET /api/planets/<code>/variants/<variant>/heightmap.png

「地形万華鏡」として、

  • 惑星メタ情報(名前・説明・サイズ)
  • PNG実体へのパス
    をMySQLに置いておき、
    Flaskは「ルーター兼ファイルサーバ」として動く、という構造はかなり素直で拡張しやすい。

この方向性で、

  • タイル制御
  • バリエーション
  • 生成ログ(どのパラメータで作ったか)
    なども一緒に持ちたくなったら、そのスキーマも一緒に設計していこう。