Roblox Studio の「Model を挿入したときどこに現れるか」「その Position はどう決まるか」は、実は 状況によってルールが変わるんです。


🎭 結論:Model の挿入位置は “文脈依存” で決まる

以下の 4 パターンで挙動が変わります。


🟦 1. エクスプローラーで右クリック → Insert(挿入)した場合

その親(Parent)にそのまま追加される

  • Workspace を右クリック → Insert → Model
    Workspace の直下に追加される
  • Folder を右クリック → Insert → Model
    その Folder の中に追加される

Position は (0, 0, 0) 付近になる

  • Model 内に PrimaryPart が設定されていない場合
    → Model の中のパーツの位置がそのまま使われる
  • PrimaryPart が設定されている場合
    → PrimaryPart の位置が (0, 0, 0) に来るように配置される

つまり、Model の内部データに依存する。


🟦 2. Toolbox からドラッグ&ドロップした場合

カメラの前方にスポーンする

Roblox Studio は「ユーザーが見ている方向」を優先するため、

  • Camera の CFrame の前方 10〜20 stud 付近
  • 地面に接地するように調整されることが多い

➤ Position は Camera に依存

例:
Camera が (0, 50, 0) を向いているなら、
Model は (0, 50, -15) 付近に出る。


🟦 3. 3D Viewport にドラッグ&ドロップした場合

ドロップした地点の地面にスナップする

  • マウスカーソルが指している Raycast のヒット位置に配置される
  • 地面がない場合は Camera 前方に落ちる

➤ Position は Raycast の結果で決まる


🟦 4. Script で Instance.new("Model") した場合

位置は “存在しない” ところから始まる

Model は 位置を持たないコンテナなので、
生成した瞬間は Position が決まらない

local m = Instance.new("Model")
m.Parent = workspace

この状態では 空の Model が Workspace に置かれるだけ

➤ パーツを入れた瞬間に位置が決まる

Model 内に Part を入れると、その Part の Position が Model の位置の基準になる。


 

Roblox Studio のアセット管理(Asset Manager)で選択中の Model を扱う場合、中心を (0,0,0) に揃える処理と、Workspace 内の“見えるフォルダ”を対話的に作る処理を分けて考えると整理しやすいです。

以下に ① 対話的に Workspace フォルダを作る関数
② 選択中 Model を原点に揃えてそのフォルダに入れる関数
を示します。


🟦 ① Workspace に「見えるフォルダ」を対話的に作る関数

Roblox Studio の Plugin / Command Bar で動くコードです。

-- Workspace にフォルダを対話的に作る
function CreateVisibleFolder()
    local name = ""
    repeat
        name = tostring(game:GetService("StarterGui"):Prompt("フォルダ名を入力してください"))
    until name and name ~= ""

    local folder = Instance.new("Folder")
    folder.Name = name
    folder.Parent = workspace

    print("フォルダを作成:", folder:GetFullName())
    return folder
end

🔍 ポイント

  • Studio には標準の入力ダイアログがないため、
    StarterGui:Prompt() のような簡易 UI を使うか、
    PluginWidget を使うのが一般的です。
  • ここでは「対話的に名前を入力する」構造を示しています。

 


🟦 ② 選択中の Model を Position(0,0,0) に揃えてフォルダに入れる関数

Asset Manager で選択した Model は Selection サービスから取得できます。

local Selection = game:GetService("Selection")

-- Model の中心を (0,0,0) に揃える
local function CenterModelAtOrigin(model)
    -- PrimaryPart が必要
    if not model.PrimaryPart then
        warn("PrimaryPart が設定されていません:", model.Name)
        return
    end

    -- 現在の中心位置
    local currentPos = model.PrimaryPart.Position

    -- 原点との差分
    local offset = Vector3.new(0,0,0) - currentPos

    -- Model 全体を移動
    model:MoveTo(model.PrimaryPart.Position + offset)
end

-- 選択中 Model をフォルダに入れる
local function MoveSelectedModelToFolder(folder)
    local selected = Selection:Get()
    if #selected == 0 then
        warn("何も選択されていません")
        return
    end

    for _, obj in ipairs(selected) do
        if obj:IsA("Model") then
            CenterModelAtOrigin(obj)
            obj.Parent = folder
            print("移動:", obj.Name, "→", folder:GetFullName())
        end
    end
end

🟦 ③ 全体の流れ(目的に最適化)

local folder = CreateVisibleFolder()
MoveSelectedModelToFolder(folder)

これで、

  1. ユーザーがフォルダ名を入力して Workspace にフォルダを作る
  2. Asset Manager で選択中の Model を原点に揃える
  3. そのフォルダに収納する

という一連の舞台裏作業が自動化できます。


 

 

🎯 PrimaryPart がない Model に自動設定する関数

以下の関数は、Model 内の Part をスキャンして、
最も“中心に近い Part”を PrimaryPart に自動設定します。

-- Model の PrimaryPart を自動設定する
local function AutoSetPrimaryPart(model)
    if not model:IsA("Model") then
        warn("Model ではありません:", model)
        return false
    end

    -- すでに PrimaryPart がある場合は何もしない
    if model.PrimaryPart then
        return true
    end

    -- Model 内の Part を収集
    local parts = {}
    for _, obj in ipairs(model:GetDescendants()) do
        if obj:IsA("BasePart") then
            table.insert(parts, obj)
        end
    end

    if #parts == 0 then
        warn("Model に BasePart がありません:", model.Name)
        return false
    end

    -- Model の中心(BoundingBox の中心)を取得
    local cf, size = model:GetBoundingBox()
    local center = cf.Position

    -- 中心に最も近い Part を探す
    local bestPart = nil
    local bestDist = math.huge

    for _, part in ipairs(parts) do
        local dist = (part.Position - center).Magnitude
        if dist < bestDist then
            bestDist = dist
            bestPart = part
        end
    end

    -- PrimaryPart に設定
    model.PrimaryPart = bestPart
    print("PrimaryPart を設定:", model.Name, "→", bestPart.Name)

    return true
end

 

🟦 PrimaryPart 自動設定 → 原点に揃える → フォルダに収納

一連の流れはこうなります。

local function PrepareModel(model)
    AutoSetPrimaryPart(model)
    CenterModelAtOrigin(model)
end

このように PrimaryPart の自動設定は前処理として必須になります。


 

これはつまり 「Model の中心(BoundingBox の中心)を“仮想的な PrimaryPart”として扱い、実際に中心にパーツがなくても、そこを基準に位置合わせしたい」 ということですね。

Roblox の Model は PrimaryPart がないと MoveTo で中心基準の移動ができないため、
中心にパーツがなくても中心を基準に動かす“疑似 PrimaryPart”処理が必要になります。

そこで、靖さんの目的に完全に合う 中心基準の移動関数を示します。


🎯 中心にパーツがなくても Model を中心基準で (0,0,0) に揃える関数

-- Model を中心基準で (0,0,0) に移動する
local function MoveModelCenterToOrigin(model)
    -- BoundingBox から中心を取得
    local cf, size = model:GetBoundingBox()
    local center = cf.Position

    -- 中心を原点に合わせるためのオフセット
    local offset = Vector3.new(0,0,0) - center

    -- Model 内のすべての BasePart を移動
    for _, obj in ipairs(model:GetDescendants()) do
        if obj:IsA("BasePart") then
            obj.Position = obj.Position + offset
        end
    end
end

🟦 この関数の特徴

✔ PrimaryPart が不要

Model.PrimaryPart がなくても動く。

✔ 中心にパーツがなくても正確に中心を使う

BoundingBox の中心を使うため、
立方体の中心が空洞でも問題なし

✔ Model 全体を“中心基準”で移動できる

MoveTo の代わりに 全パーツを相対移動する方式。


 

ここまでの流れを 完全に一つの関数群として統合し、

  • PrimaryPart がなくても中心基準で揃える
  • 中心にパーツがなくても BoundingBox の中心で揃える
  • Workspace に対話的にフォルダを作る
  • 選択中の Model をそのフォルダに入れる

という一連の“舞台セットのセンタリング → 舞台袖への収納”を
ワンアクションで実行できる形にまとめます。


🎯 **完全統合版:

「中心揃え → フォルダ作成 → フォルダに収納」**

以下は Plugin / Command Bar で動くコードです。


🟦 ① Model を中心基準で (0,0,0) に揃える(PrimaryPart 不要)

local function MoveModelCenterToOrigin(model)
    local cf, size = model:GetBoundingBox()
    local center = cf.Position
    local offset = Vector3.new(0,0,0) - center

    for _, obj in ipairs(model:GetDescendants()) do
        if obj:IsA("BasePart") then
            obj.Position = obj.Position + offset
        end
    end
end

🟦 ② Workspace に対話的にフォルダを作る

Roblox Studio には標準の入力ダイアログがないため、
ここでは 簡易的に Command Bar 用の入力として扱います。

local function CreateFolderInWorkspace(name)
    local folder = Instance.new("Folder")
    folder.Name = name
    folder.Parent = workspace
    print("フォルダ作成:", folder:GetFullName())
    return folder
end

※ 靖さんが Plugin 化するなら、ここを PluginWidget に置き換え可能。


🟦 ③ 選択中の Model を中心揃えしてフォルダに入れる

local Selection = game:GetService("Selection")

local function ProcessSelectedModels(folder)
    local selected = Selection:Get()
    if #selected == 0 then
        warn("何も選択されていません")
        return
    end

    for _, obj in ipairs(selected) do
        if obj:IsA("Model") then
            MoveModelCenterToOrigin(obj)
            obj.Parent = folder
            print("移動:", obj.Name, "→", folder:GetFullName())
        end
    end
end

🟦 ④ 全体統合:

「フォルダ名を指定 → 中心揃え → フォルダに収納」

local function CenterAndStoreSelectedModels(folderName)
    local folder = CreateFolderInWorkspace(folderName)
    ProcessSelectedModels(folder)
end

🎬 使い方(Command Bar)

CenterAndStoreSelectedModels("MyCenteredModels")

これで:

  1. Workspace に「MyCenteredModels」フォルダが作られる
  2. 選択中の Model が
    • PrimaryPart がなくても
    • 中心にパーツがなくても
      BoundingBox の中心基準で (0,0,0) に揃えられる
  3. そのフォルダに収納される

という一連の舞台裏作業が一発で完了します。


 

ここは 「既存フォルダがある場合にどう振る舞うべきか」という、
舞台袖(Workspace)管理の“運用ルール”の話になりますね。

結論から言うと、
既にフォルダがある場合の動作は、どうしたいかで分岐できる
ように設計するのが一番透明で扱いやすいです。

ただ、一般的な「期待される動作」は以下の3つのどれかです。


🎯 既存フォルダがある場合の標準的な動作パターン

🟦 パターン1:既存フォルダをそのまま使う(最も自然)

フォルダ名が一致したら 新しく作らず、そのフォルダに入れる

これは舞台袖の“既存区画”にセットを収納するイメージ。


🟦 パターン2:同名フォルダがあっても新しく作る(バージョン管理向け)

例:

  • Models
  • Models (2)
  • Models (3)

のように 自動で連番をつけて新規作成する。


🟦 パターン3:ユーザーに選ばせる(対話的)

  • 既存フォルダを使う
  • 新しく作る
  • 別のフォルダ名にする

「外部化・可視化」思想に最も合うのはこれ。


🎯 統合スクリプトに最適な動作

あなたのワークフロー(舞台構造の整理・可視化)を考えると、
既存フォルダがあればそれを使う
という動作が最も自然で、混乱も少ないです。

そこで、統合スクリプトを以下のように改良します。


🟦 既存フォルダがあればそれを使い、なければ作る関数

local function GetOrCreateFolder(name)
    -- Workspace 内に同名フォルダがあるか探す
    for _, child in ipairs(workspace:GetChildren()) do
        if child:IsA("Folder") and child.Name == name then
            print("既存フォルダを使用:", child:GetFullName())
            return child
        end
    end

    -- なければ新規作成
    local folder = Instance.new("Folder")
    folder.Name = name
    folder.Parent = workspace
    print("新規フォルダ作成:", folder:GetFullName())
    return folder
end

🟦 統合処理(中心揃え → フォルダ収納)

local function CenterAndStoreSelectedModels(folderName)
    local folder = GetOrCreateFolder(folderName)
    ProcessSelectedModels(folder)
end