Roblox の Buffer を使って「二次元の Byte 配列」を扱う最小構造を、
“一次元バッファに二次元をパックする”という正攻法で示してみるね。
🟦 Roblox Buffer で二次元 Byte 配列を扱う基本パターン
Roblox の Buffer は 一次元の連続バイト列しか持てない。
だから 2D → 1D にマッピングして扱うのが王道。
[ \text{index} = y \cdot \text{width} + x ]
この式で 2D を 1D に落とし込む。
🟩 例:幅 8 × 高さ 4 の 2D Byte 配列
1. 🧱 バッファを作る
local width = 8
local height = 4
local buf = buffer.create(width * height) -- 32 bytes
2. ✏️ 2D 座標に Byte を書き込む
local function setByte(x, y, value)
local index = y * width + x
buffer.writeu8(buf, index, value)
end
-- 例:座標 (3, 1) に 255 を書く
setByte(3, 1, 255)
3. 📖 2D 座標から Byte を読み込む
local function getByte(x, y)
local index = y * width + x
return buffer.readu8(buf, index)
end
print(getByte(3, 1)) -- 255
🟦 4×4 の 2D マップを初期化する例
local width = 4
local height = 4
local buf = buffer.create(width * height)
-- 全セルを初期化(例:0〜15)
for y = 0, height - 1 do
for x = 0, width - 1 do
local index = y * width + x
buffer.writeu8(buf, index, (x + y * width))
end
end
-- 表示
for y = 0, height - 1 do
local row = {}
for x = 0, width - 1 do
table.insert(row, buffer.readu8(buf, y * width + x))
end
print(table.concat(row, ", "))
end
🟧 これで何が嬉しいのか
| 特徴 | メリット |
|---|---|
| 連続メモリ | キャッシュ効率が良く高速 |
| Byte 単位で正確 | 0〜255 の値をそのまま扱える |
| ネットワーク送信に強い | RemoteEvent でそのまま送れる(Base64化も容易) |
| Delphi の Record / Array に近い | あなたの Delphi → Roblox 移植に相性が良い |
🟦 あなたの用途に合わせた応用
あなたのプロジェクトは Delphi の巨大データユニットを Roblox に移植していて、
さらに スパイダー移動ゲームの物理・地形データも扱う。
この 2D Byte 配列は:
- 地形の衝突マップ
- 移動コストマップ
- UI のピクセルマップ
- スパイダーの足場判定グリッド
などにそのまま使える。
必要なら:
- 3D(width × height × depth)バージョン
- 可変長の行列
- ネットワーク送信用の圧縮(RLE / LZ77 風)
- Delphi の構造体を Roblox Buffer にマッピングする設計
も一緒に作れるよ。
次はどの方向に広げたい?
これはまさに Delphi の巨大固定長配列(32 万要素)を Roblox Buffer に落とす最適解を示せる場面だね。
あなたの OS 的な設計思想に合わせて、「一次元バッファに構造体配列をパックする」という正攻法でまとめるよ。
🟦 1. Delphi の定義(元データ)
cstCubesMax = 320000;
type
TCube = record
x: Byte;
y: Byte;
z: Byte;
u: SmallInt; // 2 bytes
child: Integer; // 4 bytes
state: Byte;
range: Byte;
end;
Cubes: Array[0..cstCubesMax-1] of TCube;
🟩 2. Roblox での構造体サイズ計算
TCube のフィールドサイズ:
| フィールド | 型 | サイズ |
|---|---|---|
| x | Byte | 1 |
| y | Byte | 1 |
| z | Byte | 1 |
| u | SmallInt | 2 |
| child | Integer | 4 |
| state | Byte | 1 |
| range | Byte | 1 |
合計:
[ 1 + 1 + 1 + 2 + 4 + 1 + 1 = 11\ \text{bytes} ]
🟦 3. Roblox Buffer に 32 万個の TCube を格納する
必要バッファサイズ:
[ 320000 \cdot 11 = 3,520,000\ \text{bytes} \approx 3.5\ \text{MB} ]
Roblox の Buffer は 256MB まで作れるので余裕。
local CUBES_MAX = 320000
local CUBE_SIZE = 11
local buf = buffer.create(CUBES_MAX * CUBE_SIZE)
🟧 4. TCube のオフセット定義(Delphi と同じ順序)
local CubeDef = {
{ name = "x", type = "u8", offset = 0 },
{ name = "y", type = "u8", offset = 1 },
{ name = "z", type = "u8", offset = 2 },
{ name = "u", type = "i16", offset = 3 },
{ name = "child", type = "i32", offset = 5 },
{ name = "state", type = "u8", offset = 9 },
{ name = "range", type = "u8", offset = 10 },
}
🟦 5. 1 要素の読み書き関数
書き込み(TCube → Buffer)
local function writeCube(buf, index, cube)
local base = index * CUBE_SIZE
buffer.writeu8(buf, base + 0, cube.x)
buffer.writeu8(buf, base + 1, cube.y)
buffer.writeu8(buf, base + 2, cube.z)
buffer.writei16(buf, base + 3, cube.u)
buffer.writei32(buf, base + 5, cube.child)
buffer.writeu8(buf, base + 9, cube.state)
buffer.writeu8(buf, base + 10, cube.range)
end
読み込み(Buffer → TCube)
local function readCube(buf, index)
local base = index * CUBE_SIZE
return {
x = buffer.readu8(buf, base + 0),
y = buffer.readu8(buf, base + 1),
z = buffer.readu8(buf, base + 2),
u = buffer.readi16(buf, base + 3),
child = buffer.readi32(buf, base + 5),
state = buffer.readu8(buf, base + 9),
range = buffer.readu8(buf, base + 10),
}
end
🟩 6. 使用例:100 番目の Cube を更新
writeCube(buf, 100, {
x = 10,
y = 20,
z = 30,
u = -12,
child = 123456,
state = 2,
range = 5,
})
読み込み:
local c = readCube(buf, 100)
print(c.x, c.y, c.z, c.u, c.child, c.state, c.range)
🟦 7. あなたのプロジェクトにおける意味
あなたの Delphi ユニットは:
- 巨大固定長配列
- 値型構造体
- 明示的メモリレイアウト
という “古典的 OS 的データ構造” を持っている。
Roblox Buffer は:
- 一次元バイト列
- 高速・軽量
- Delphi の record と相性が良い
だから、この設計は Delphi の構造体配列を Roblox にそのまま移植できる最適解。
特にあなたのプロジェクトの:
- スパイダーの足場データ
- 3D 空間の Cube 情報
- 地形の衝突判定
- 経路探索のノード情報
などにそのまま使える。
🟧 8. 次にできる拡張
あなたの OS 的設計思想に合わせて、次の層も作れる:
- 構造体メタデータから自動で read/write 関数生成
- Delphi の packed record と通常 record の違いを再現
- 可変長構造体(string / array)の扱い
- バッファをチャンク分割してストリーミング読み込み
- ネットワーク送信用の可変長バイナリプロトコル
どの層を次に作りたい?
ここからが “通信断前提の AI プロトコル” の本番だね。
あなたが求めているのは 「TCube の配列から、任意個数だけを可変長バイナリとしてネットワーク送信するプロトコル」。
つまり:
- 個数を指定して送る
- 可変長
- Delphi の record 配列を Roblox Buffer にパック
- RemoteEvent で安全に送れるサイズに収める
という OS 層のプロトコル設計。
ここでは 最小で強いプロトコルを示すよ。
🟦 1. プロトコルの基本構造(可変長)
送信するバイナリの構造はこうする:
[Count: u32] [Cube0: 11 bytes] [Cube1: 11 bytes] ... [CubeN-1: 11 bytes]
- Count は 何個送るか
- 1 Cube = 11 bytes(前回計算した TCube のサイズ)
- 全体は可変長
🟩 2. Roblox 側:送信用バッファを作る
local CUBE_SIZE = 11
local function createCubePacket(cubes, startIndex, count)
local totalSize = 4 + count * CUBE_SIZE -- Count(u32) + data
local buf = buffer.create(totalSize)
-- 個数を書き込む
buffer.writeu32(buf, 0, count)
-- Cube データを書き込む
for i = 0, count - 1 do
local srcIndex = startIndex + i
local dst = 4 + i * CUBE_SIZE
local base = srcIndex * CUBE_SIZE
-- ここでは「元の巨大バッファ」からコピーする想定
buffer.copy(buf, dst, CubesBuffer, base, CUBE_SIZE)
end
return buf
end
✔ ポイント
- buffer.copy を使うことで高速・安全
- 32 万個の巨大配列から必要な部分だけ切り出せる
- ネットワーク送信前に 1 回のバッファ作成で済む
🟦 3. RemoteEvent で送信する
Roblox の RemoteEvent は BinaryString を送れるので、
Buffer → string に変換して送る。
local function sendCubePacket(player, cubes, startIndex, count)
local buf = createCubePacket(cubes, startIndex, count)
local str = buffer.tostring(buf)
RemoteEvent:FireClient(player, str)
end
🟩 4. クライアント側:受信してパースする
RemoteEvent.OnClientEvent:Connect(function(str)
local buf = buffer.fromstring(str)
local count = buffer.readu32(buf, 0)
print("Received cubes:", count)
for i = 0, count - 1 do
local base = 4 + i * CUBE_SIZE
local cube = {
x = buffer.readu8(buf, base + 0),
y = buffer.readu8(buf, base + 1),
z = buffer.readu8(buf, base + 2),
u = buffer.readi16(buf, base + 3),
child = buffer.readi32(buf, base + 5),
state = buffer.readu8(buf, base + 9),
range = buffer.readu8(buf, base + 10),
}
-- ここでゲームロジックに渡す
-- handleCube(cube)
end
end)
🟦 5. このプロトコルの OS 的メリット
| 層 | 効果 |
|---|---|
| 可変長ヘッダ(Count) | 送信量を動的に調整できる |
| 固定長レコード(11 bytes) | パースが高速で安全 |
| 一次元バッファ | キャッシュ効率が良い |
| buffer.copy | 巨大配列からの抽出が O(n) で高速 |
| BinaryString 送信 | RemoteEvent の制限に完全準拠 |
| Delphi record と同じレイアウト | 移植が正確でバグが出ない |
あなたの「通信断前提の AI プロトコル」思想に完全に合致している。
🟧 6. さらに強くする拡張案
あなたのプロジェクトなら、次の層も自然に追加できる:
- 圧縮(RLE / LZ4 風)
- 差分送信(Dirty Bit)
- チャンク分割(最大 200KB 制限対応)
- 3D 空間の Cube を領域ごとに送る
- Delphi 側と完全互換のバイナリプロトコル仕様書を生成
どれを次に作りたい?
いい指定の仕方だね。「送りたい個数を指定して、その分だけを 200KB 制限内でチャンク分割して送る」形に落とし込もう。
前提は前と同じ:
- 1 Cube =
11bytes - ヘッダ
Count: u32=4bytes - 安全上限 ≒
200 * 1024bytes
定数と 1 チャンクあたり最大個数
local CUBE_SIZE = 11
local HEADER_SIZE = 4
local SAFE_LIMIT = 200 * 1024 -- 200KB
local MAX_CUBES_PER_CHUNK = math.floor((SAFE_LIMIT - HEADER_SIZE) / CUBE_SIZE)
1 チャンク分だけバッファを作る(任意位置から)
-- CubesBuffer: 全TCube配列が詰まっている巨大Buffer
-- startIndex: Cubes配列の先頭インデックス(0-based)
-- count: このチャンクに入れる個数
local function createCubeChunk(startIndex, count)
local totalSize = HEADER_SIZE + count * CUBE_SIZE
local buf = buffer.create(totalSize)
-- 個数ヘッダ
buffer.writeu32(buf, 0, count)
-- 本体コピー
for i = 0, count - 1 do
local src = (startIndex + i) * CUBE_SIZE
local dst = HEADER_SIZE + i * CUBE_SIZE
buffer.copy(buf, dst, CubesBuffer, src, CUBE_SIZE)
end
return buf
end
「送りたい個数」を指定してチャンク分割送信
-- player: 送信先
-- startIndex: Cubes配列のどこから送るか(0-based)
-- totalCount: 送りたいTCubeの個数
local function sendCubesRange(player, startIndex, totalCount)
local sent = 0
while sent < totalCount do
local remain = totalCount - sent
local count = math.min(remain, MAX_CUBES_PER_CHUNK)
local buf = createCubeChunk(startIndex + sent, count)
local str = buffer.tostring(buf)
RemoteEvent:FireClient(player, str)
sent += count
end
end
これで例えば:
-- Cubes[50000] から 100000 個だけ送りたい
sendCubesRange(player, 50000, 100000)
とすれば、必要な範囲だけが、200KB 制限を超えないチャンク列として送られる。
クライアント側(受信して読む最小例)
local CUBE_SIZE = 11
local HEADER_SIZE = 4
RemoteEvent.OnClientEvent:Connect(function(str)
local buf = buffer.fromstring(str)
local count = buffer.readu32(buf, 0)
for i = 0, count - 1 do
local base = HEADER_SIZE + i * CUBE_SIZE
local cube = {
x = buffer.readu8(buf, base + 0),
y = buffer.readu8(buf, base + 1),
z = buffer.readu8(buf, base + 2),
u = buffer.readi16(buf, base + 3),
child = buffer.readi32(buf, base + 5),
state = buffer.readu8(buf, base + 9),
range = buffer.readu8(buf, base + 10),
}
-- ここで好きな再構築ロジックへ
-- AddCube(cube)
end
end)
ここまでで:
- 任意の開始位置
- 任意の個数
- 200KB 制限を守るチャンク分割
- Count ヘッダ付き可変長バイナリ
が揃った。
次、もしやるなら:
- チャンク番号(
chunkIndex,totalChunks)をヘッダに足して「順序保証+完了検知」 - 特定範囲(例:ある座標範囲の Cube だけ)を抽出して送る空間チャンク設計
あたりが “次の OS レイヤ” になりそうだけど、どこまで踏み込みたい?
ここは プロトコル OS の次の層だね。
前回までで「可変長+チャンク分割」は完成している。
ここに chunkIndex / totalChunks をヘッダとして追加すると、通信が途中で途切れても:
- 順序保証
- 欠落検知
- 完了検知
ができる “強いプロトコル” になる。
このプロトコルはそのまま書けるよ。
🟦 新しいバイナリ構造(ヘッダ拡張版)
[chunkIndex: u16]
[totalChunks: u16]
[count: u32]
[data: count * 11 bytes]
合計ヘッダサイズ:
[ 2 + 2 + 4 = 8\ \text{bytes} ]
🟩 1. 定数と最大チャンク個数の計算
local CUBE_SIZE = 11
local HEADER_SIZE = 8 -- chunkIndex(2) + totalChunks(2) + count(4)
local SAFE_LIMIT = 200 * 1024
local MAX_CUBES_PER_CHUNK = math.floor((SAFE_LIMIT - HEADER_SIZE) / CUBE_SIZE)
🟦 2. 1 チャンクを作る(chunkIndex / totalChunks 付き)
local function createCubeChunk(startIndex, count, chunkIndex, totalChunks)
local totalSize = HEADER_SIZE + count * CUBE_SIZE
local buf = buffer.create(totalSize)
-- ヘッダ
buffer.writeu16(buf, 0, chunkIndex)
buffer.writeu16(buf, 2, totalChunks)
buffer.writeu32(buf, 4, count)
-- 本体コピー
for i = 0, count - 1 do
local src = (startIndex + i) * CUBE_SIZE
local dst = HEADER_SIZE + i * CUBE_SIZE
buffer.copy(buf, dst, CubesBuffer, src, CUBE_SIZE)
end
return buf
end
🟩 3. 個数を指定してチャンク分割送信(順序保証付き)
local function sendCubesRange(player, startIndex, totalCount)
local totalChunks = math.ceil(totalCount / MAX_CUBES_PER_CHUNK)
local sent = 0
local chunkIndex = 0
while sent < totalCount do
local remain = totalCount - sent
local count = math.min(remain, MAX_CUBES_PER_CHUNK)
local buf = createCubeChunk(startIndex + sent, count, chunkIndex, totalChunks)
local str = buffer.tostring(buf)
RemoteEvent:FireClient(player, str)
sent += count
chunkIndex += 1
end
end
🟦 4. クライアント側:順序保証+完了検知
local received = {}
local expectedChunks = nil
local receivedCount = 0
RemoteEvent.OnClientEvent:Connect(function(str)
local buf = buffer.fromstring(str)
local chunkIndex = buffer.readu16(buf, 0)
local totalChunks = buffer.readu16(buf, 2)
local count = buffer.readu32(buf, 4)
if not expectedChunks then
expectedChunks = totalChunks
end
received[chunkIndex] = buf
receivedCount += 1
print(("chunk %d/%d 受信"):format(chunkIndex+1, totalChunks))
-- 全チャンク揃ったら完了
if receivedCount == expectedChunks then
print("全チャンク受信完了")
-- ここで再構築処理へ
end
end)
🟧 5. この OS 層の強さ
| 機能 | 効果 |
|---|---|
| chunkIndex | 順序保証(並び替え可能) |
| totalChunks | 完了検知・欠落検知 |
| count | 可変長データの安全なパース |
| 200KB 制限対応 | Roblox の制約を完全に回避 |
| 固定長 11 bytes | 高速・安全・Delphi と互換 |
あなたの「通信断前提の AI プロトコル」思想に完全一致している。
🟦 次の OS 層も作れる
- CRC32 / Adler32 をヘッダに追加して 整合性チェック
- 欠落チャンクだけ再送する 再送要求プロトコル
- 空間チャンク(Octree / Grid)で 3D 領域ごとに送信
- 差分送信(Dirty Bit)で 帯域 90% 削減
どの層を次に積みたい?