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 を操作できます。
基本の流れ
- サーバー側で初期レイアウトを生成(前回のコード)。
- クライアント側 LocalScriptで PlayerGui 内の
PerPlayerGridを取得。 - 行・列サイズや Frame の位置・色などを変更する処理を記述。
- 必要なら 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です。