Robloxでプレイヤー毎に異なる重なり可能な可変サイズFrameグリッド

各プレイヤーの PlayerGui に、サイズが異なる Frame を行列状に並べつつ、重なりも許容する UI を生成する最小構成です。UIGridLayout は等サイズ前提なので使わず、セルサイズ配列に基づく手動配置で可変・重なり・スパンを実現します。


設計概要

  • レイアウト定義: 行ごとの高さ配列、列ごとの幅配列、各フレームのセル座標・スパン・色・ZIndex・オフセット。
  • プレイヤー毎差異: ModuleScript にユーザーID別の定義を持たせ、未定義はデフォルトにフォールバック。
  • 重なり制御: 同一座標に複数 Frame を生成可能。前後は ZIndex で制御。
  • 座標計算: 行高・列幅の累積和から原点を算出し、スパン分を合計してサイズを計算。
  • 安全配置: AnchorPoint(0,0)、UDim2(0,px,0,px) を用いたピクセル固定で環境差を回避。

レイアウト定義の例(ModuleScript)

-- ReplicatedStorage/GridLayouts.lua
local GridLayouts = {}

-- デフォルトレイアウト(全プレイヤーに適用、個別定義があればそちらが優先)
GridLayouts.default = {
    rowHeights = { 60, 80, 50, 70 },     -- 各行の高さ(px)
    colWidths  = { 90, 120, 100, 80 },   -- 各列の幅(px)

    -- フレーム定義の配列
    items = {
        -- 基本: r,c は 1 始まり。rowSpan/colSpan 省略時は 1。
        { r = 1, c = 1, rowSpan = 1, colSpan = 2, color = Color3.fromRGB(255, 120, 120), z = 2 },
        { r = 1, c = 2, rowSpan = 2, colSpan = 1, color = Color3.fromRGB(120, 180, 255), z = 1, offset = Vector2.new(10, 0) },
        { r = 2, c = 3, rowSpan = 2, colSpan = 2, color = Color3.fromRGB(160, 220, 160), z = 3 },

        -- 意図的な重なり例:同じセル領域に別フレーム
        { r = 2, c = 3, rowSpan = 1, colSpan = 1, color = Color3.fromRGB(255, 220, 100), z = 4, offset = Vector2.new(15, 10) },

        -- ばらついたサイズと位置
        { r = 3, c = 1, rowSpan = 1, colSpan = 1, color = Color3.fromRGB(200, 200, 255), z = 2 },
        { r = 4, c = 2, rowSpan = 1, colSpan = 3, color = Color3.fromRGB(180, 120, 220), z = 1 },
    }
}

-- プレイヤー個別(例: ユーザーIDで上書き)
GridLayouts.byUserId = {
    [1234567890] = {
        rowHeights = { 50, 50, 100, 90 },
        colWidths  = { 100, 110, 70, 140 },
        items = {
            { r = 1, c = 1, rowSpan = 2, colSpan = 2, color = Color3.fromRGB(240, 140, 140), z = 3 },
            { r = 2, c = 2, rowSpan = 1, colSpan = 3, color = Color3.fromRGB(120, 200, 240), z = 2, offset = Vector2.new(-8, 12) },
            { r = 3, c = 3, rowSpan = 2, colSpan = 1, color = Color3.fromRGB(140, 240, 160), z = 4 },
        }
    }
}

return GridLayouts

サーバースクリプト(PlayerGui に生成)

-- ServerScriptService/GridBuilder.server.lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local GridLayouts = require(ReplicatedStorage:WaitForChild("GridLayouts"))

-- 累積和でセル原点とスパンサイズを求めるユーティリティ
local function sumRange(arr, startIndex, count)
    local s = 0
    for i = startIndex, startIndex + count - 1 do
        s += arr[i] or 0
    end
    return s
end

local function sumBefore(arr, index)
    local s = 0
    for i = 1, index - 1 do
        s += arr[i] or 0
    end
    return s
end

local function buildGridForPlayer(player)
    -- レイアウト取得(個別→デフォルト)
    local layout = GridLayouts.byUserId[player.UserId] or GridLayouts.default
    local rowHeights = layout.rowHeights
    local colWidths  = layout.colWidths
    local items      = layout.items

    -- ScreenGui 作成
    local screenGui = Instance.new("ScreenGui")
    screenGui.Name = "PerPlayerGrid"
    screenGui.ResetOnSpawn = false
    screenGui.IgnoreGuiInset = true
    screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
    screenGui.Parent = player:WaitForChild("PlayerGui")

    -- コンテナ Frame(必要ならサイズ調整)
    local container = Instance.new("Frame")
    container.Name = "GridContainer"
    container.BackgroundTransparency = 1
    container.AnchorPoint = Vector2.new(0, 0)
    container.Position = UDim2.new(0, 20, 0, 20)
    container.Size = UDim2.new(0, sumRange(colWidths, 1, #colWidths), 0, sumRange(rowHeights, 1, #rowHeights))
    container.Parent = screenGui

    -- 各フレーム生成(重なり許容)
    for i, def in ipairs(items) do
        local r = def.r
        local c = def.c
        local rSpan = def.rowSpan or 1
        local cSpan = def.colSpan or 1
        local color = def.color or Color3.fromRGB(220, 220, 220)
        local z     = def.z or 1
        local offset = def.offset or Vector2.new(0, 0)

        -- 原点(累積和)
        local x0 = sumBefore(colWidths, c)
        local y0 = sumBefore(rowHeights, r)

        -- スパンサイズ(合計)
        local w = sumRange(colWidths, c, cSpan)
        local h = sumRange(rowHeights, r, rSpan)

        local frame = Instance.new("Frame")
        frame.Name = string.format("Cell_%d_%d_%d", r, c, i)
        frame.BackgroundColor3 = color
        frame.BorderSizePixel = 0
        frame.ZIndex = z
        frame.AnchorPoint = Vector2.new(0, 0)
        frame.Position = UDim2.new(0, x0 + offset.X, 0, y0 + offset.Y)
        frame.Size     = UDim2.new(0, w, 0, h)

        -- 見やすさのため角丸など(任意)
        local corner = Instance.new("UICorner")
        corner.CornerRadius = UDim.new(0, 8)
        corner.Parent = frame

        frame.Parent = container
    end
end

-- プレイヤー参加時に構築
Players.PlayerAdded:Connect(function(player)
    -- プレイヤー毎に異なる並びと重なりを反映
    buildGridForPlayer(player)
end)

-- 既存プレイヤー(Play Solo など)
for _, p in ipairs(Players:GetPlayers()) do
    buildGridForPlayer(p)
end

補足(プレイヤー毎の差異の付け方)

  • 個別レイアウト: ModuleScript の byUserId にユーザーIDキーでレイアウトを追加。
  • 動的変更: サーバーでレイアウトを差し替え、buildGridForPlayer を再呼び出しすれば UI を再構築可能。
  • 重なり順: ZIndex を高くしたフレームが前面。重なりを視覚化したい場合は色の透明度(BackgroundTransparency)や半透明色も活用できます。

このまま貼り付ければ、各プレイヤーの PlayerGui に「サイズが異なり、重ねも許容される行列状の Frame 群」が生成されます。

 

なるほど、サーバー側で生成したグリッドを「プレイヤー自身がクライアント側で動的に変更できる」ようにしたいんですね。Roblox では LocalScript を使って PlayerGui 内の UI を操作できます。


基本の流れ

  1. サーバー側で初期レイアウトを生成(前回のコード)。
  2. クライアント側 LocalScriptで PlayerGui 内の PerPlayerGrid を取得。
  3. 行・列サイズや Frame の位置・色などを変更する処理を記述。
  4. 必要なら RemoteEvent を使ってサーバーと同期(例: 他プレイヤーにも反映したい場合)。

クライアント側の例(LocalScript)

-- StarterPlayerScripts/GridClient.local.lua
local Players = game:GetService("Players")
local player = Players.LocalPlayer

-- PlayerGui 内の ScreenGui を取得
local screenGui = player:WaitForChild("PlayerGui"):WaitForChild("PerPlayerGrid")
local container = screenGui:WaitForChild("GridContainer")

-- 例: すべての Frame を少し右下にずらす
for _, frame in ipairs(container:GetChildren()) do
    if frame:IsA("Frame") then
        frame.Position = frame.Position + UDim2.new(0, 20, 0, 20)
    end
end

-- 例: 特定の Frame の色を変更
local target = container:FindFirstChild("Cell_2_3_3") -- 名前はサーバー側生成時のもの
if target then
    target.BackgroundColor3 = Color3.fromRGB(255, 200, 50)
end

-- 例: 新しい Frame を追加(クライアント専用)
local newFrame = Instance.new("Frame")
newFrame.Size = UDim2.new(0, 100, 0, 60)
newFrame.Position = UDim2.new(0, 50, 0, 50)
newFrame.BackgroundColor3 = Color3.fromRGB(120, 240, 180)
newFrame.ZIndex = 5
newFrame.Parent = container

ポイント

  • LocalScript は StarterPlayerScripts や StarterGui に置くと、各プレイヤーのクライアントで実行されます。
  • クライアント側で変更した UI は そのプレイヤーだけに見える。他プレイヤーには影響しません。
  • 他プレイヤーにも反映したい場合は、RemoteEvent を使ってサーバーに通知し、サーバー側で UI を再構築する必要があります。
  • クライアント専用の「個人カスタマイズ UI」を作るなら、LocalScript 内で自由に変更すればOKです。