ソリッドモデリング

*このコンテンツは、ベータ版のAI(人工知能)を使用して翻訳されており、エラーが含まれている可能性があります。このページを英語で表示するには、 こちら をクリックしてください。

ソリッドモデリングは、BasePartsをユニークな方法で結合して、より複雑な形状を形成するプロセスです。これには、一般的に構成的ソリッドジオメトリ(CSG)として知られるブール演算交差、および減算が含まれます。スタジオ、プラグイン、およびゲーム内の両方で、サーバーとクライアントの両方でソリッドモデリングを実行できます。

ブールCSGに加えて、ソリッドモデリングはメッシュもサポートしており、水密である限り、スイープフラグメントのような操作を使用して、プレイヤーがジオメトリをスライス、カット、破壊してユニークなゲームプレイの相互作用を実現できます。

MeshPartSweepPartAsync()で作成されました
MeshPartFragmentAsync()で破壊されました

水密ジオメトリ

ソリッドモデリング操作は、水密ジオメトリでのみ機能します。技術的には、メッシュが水密であるとは、閉じていて、マニフォールドであり、自己交差していないことを意味します。これらの用語には厳密な定義がありますが、以下は簡単なルールです:

  • には「内側」と「外側」の側面が必要です。これらは、面の三つの頂点の順序によって決まります。
  • エッジは正確に二つの三角形によって共有されなければなりません。これは、メッシュに穴があってはならないことを意味します。穴の周りのエッジは一つの三角形しか持たないからです。
  • 面は他の面を通過してはなりません。
  • 隣接する三角形は、どちらの側が「外側」側であるかに同意しなければなりません。
  • 頂点は正確に一つの隣接三角形のファンを持たなければなりません。

以下の各例は、記載された理由により非水密です:

ソリッドモデリングシステムは、メッシュの特定の小さな問題を自動的に修復できますが、一般的に、メッシュが水密でない場合、API呼び出しは失敗します。既存の非水密メッシュを修復するための一律の方法はありませんが、3D Print ToolboxMesh Repair Toolsなど、役立つBlenderプラグインがいくつかあります。別のオプションとして、Meshlabにも、メッシュをマニフォールドにするための非常に便利なツールが組み込まれています。これは、メッシュが水密であるための主な要件です。

メッシュが水密にするのが非常に難しいことを確認する方法の一つは、スタジオであらゆる角度からそれを見て、メッシュのMeshPart.DoubleSidedプロパティを有効にしたり無効にしたりしてみることです。何らかの違いが見える場合、そのメッシュは単なるシェルであり、上記のツールは、メッシュの内側と外側の空間を推測できないため、機能しません。ただし、薄いメッシュが必要で、メッシュの寸法を正確に同じに保つことが重要でない場合は、BlenderのSolidify modifierを使用して、シェルをわずかに厚くして水密メッシュにすることができます。

DoubleSided = false

スタジオでのソリッドモデリング

モデルタブのツールバー内の4つのツールを使用して、3つの基本的なブール演算を実行できます。

スタジオのツールバーに強調表示されたソリッドモデリングツール。
ツールショートカット説明
ShiftCtrlG (Windows)
ShiftG (Mac)
二つ以上の部品を結合して一つのソリッド和を形成します。
交差ShiftCtrlI (Windows)
ShiftI (Mac)
重なり合う部品を一つのソリッド交差にします。
分離ShiftCtrlU (Windows)
ShiftU (Mac)
和または交差を元の個々の部品に戻します。
減算ShiftCtrlN (Windows)
ShiftN (Mac)
部品を減算し、穴や凹みを作るのに便利です。

ツールは、二つ以上の部品を結合して一つのソリッドUnionOperationを形成します。

ブロックとシリンダー。
個々の部品
一つのオブジェクトに結合されたブロックとシリンダー。
和の結果

部品を和に結合するには:

  1. 結合したいすべての部品を選択します。
  2. ボタンをクリックします。すべての部品が一つのソリッドUnionOperationに結合され、その名前はになります。

交差

交差ツールは、重なり合う部品を一つのソリッドIntersectOperationにします。

ブロックとシリンダー。
個々の部品
一つのオブジェクトに結合されたブロックとシリンダー。
交差の結果

重なり合う部品を交差させるには:

  1. 交差させたいすべての部品を選択します。
  2. 交差ボタンをクリックします。すべての部品が一つのソリッドIntersectOperationに結合され、その名前は交差になります。

減算

減算ツールは、部品を減算して、と結合したときに、減算された部品の形状が他の部品から引かれるようにします。

ブロックとシリンダー。
ブロックと減算されたシリンダー
一つのオブジェクトに結合されたブロックとシリンダー。
減算の結果

他の重なり合う部品から部品を減算するには:

  1. 他の部品から減算したい部品を選択します。
  2. 減算をクリックします。部品はその状態を示すために赤みがかった透明になります。
  3. 減算された部品と、減算したい部品を選択します。
  4. をクリックします。減算された部品が含まれる重なり合う部品から切り取られます。

分離

分離ツールは、UnionOperationを元の個々の部品に戻します。これは、和や交差の「元に戻す」ツールとして機能します。

和または交差を元の個々の部品に戻すには:

  1. 和または交差を選択します。
  2. 分離をクリックします。部品は元の形に戻ります。

ゲーム内ソリッドモデリング

ゲームが実行中の間や、GeometryServiceメソッドを通じてプラグインからソリッドモデリング操作を実行することもできます。

コア操作

スタジオの組み込み操作ツールと同様に、GeometryServiceメソッドを使用して、UnionAsync()IntersectAsync()、およびSubtractAsync()のような基本的なブール演算を実行できます。たとえば、次のスクリプトは、SubtractAsync()を使用して、一つの部品の体積を別の部品から減算します。

一つの部品を別の部品から減算
local GeometryService = game:GetService("GeometryService")
local mainPart = Instance.new("Part")
local otherPart = Instance.new("Part")
otherPart.Position = Vector3.new(1, 0.5, 1)
local success, newParts = pcall(function()
return GeometryService:SubtractAsync(mainPart, {otherPart})
end)
if success and newParts then
for _, newPart in pairs(newParts) do
newPart.Parent = workspace
end
end

さらに示すために、次のコードサンプルは、mainPartのジオメトリとotherParts配列内の部品を結合し、操作に関与した元の部品を破壊します。UnionAsync()の呼び出しをIntersectAsync()またはSubtractAsync()に置き換えて、他のブール演算を実行できます。

複数の部品のジオメトリを結合
local GeometryService = game:GetService("GeometryService")
local mainPart = workspace.BlueBlock
local otherParts = { workspace.PurpleCylinder }
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = false
}
local success, newParts = pcall(function()
return GeometryService:UnionAsync(mainPart, otherParts, options)
end)
if success and newParts then
-- 結果の部品を再親子化/再配置するためにループ
for _, newPart in pairs(newParts) do
newPart.Parent = mainPart.Parent
newPart.CFrame = mainPart.CFrame
newPart.Anchored = mainPart.Anchored
end
-- 元の部品を破壊
mainPart.Parent = nil
mainPart:Destroy()
for _, otherPart in pairs(otherParts) do
otherPart.Parent = nil
otherPart:Destroy()
end
end

エンジンレベルの部品減算メソッドはありませんが、スクリプトやプラグイン内でrbxNegateタグを追加することで、スタジオの減算ツールボタンに相当する減算を実行できます。

スクリプトによる部品の減算
local CollectionService = game:GetService("CollectionService")
CollectionService:AddTag(workspace.Part, "rbxNegate")

BasePart:UnionAsync()/BasePart:IntersectAsync()/BasePart:SubtractAsync()と比較して、GeometryServiceメソッドは以下のように異なります:

  • 出力は単一のインスタンスではなく、インスタンスの配列です。
  • 入力部品はシーンに親子化されている必要はなく、バックグラウンド操作が可能です。
  • SplitApartオプションがtrue(デフォルト)に設定されている場合、各異なるボディはそれぞれのPartOperation/MeshPartとして返されます。
  • 返されたすべての部品はメイン部品の座標空間にあり、そのPVInstance.Origin位置はメイン部品と同じです。これにより、メッシュの頂点は操作前と同じ位置に保たれますが、返された部品の(0, 0, 0)が必ずしもそのボディの中心にあるわけではありません。

スイープ部品

SweepPartAsync()メソッドは、指定された一連のCFrame位置を通して引きずられた入力部品の形状を持つMeshPartを作成します。このメソッドは、スライスやカットの相互作用を実行するのに非常に便利です。

入力はPartPartOperation、またはMeshPartであることができます。結果の形状は、各隣接ペアの凸包の和として定義されます。単一のCFrameのみが提供される場合、結果は入力部品の凸包になります。

このメソッドがどのように機能するかを示すために、次のコードサンプルは、球を一連のCFrame位置を通してスイープしてスパイラルを作成します。

スパイラルに沿ったスイープ部品
local GeometryService = game:GetService("GeometryService")
local inputPart = Instance.new("Part")
inputPart.Shape = Enum.PartType.Ball
local cframeList = {}
for i = 1, 50 do
local rotation = CFrame.Angles(0, i * 0.5, 0)
local position = Vector3.new(0, i * 0.1, -1)
table.insert(cframeList, rotation * CFrame.new(position))
end
local success, sweptPart = pcall(function()
return GeometryService:SweepPartAsync(inputPart, cframeList)
end)
if success and sweptPart then
sweptPart.Parent = workspace
end
球をスイープして作成されたスパイラル形状。
ブロックから減算された半透明の曲線形状。

この例では、SweepPartAsync()を使用して、プレイヤーのマウス位置に基づいて剣の動きを実現するスライスゲームプレイ機能を達成します。ユーザーのマウスの動きはCFramesのリストとして記録され、SweepPartAsync()はこのデータからスライスメッシュを構築し、スライスメッシュはヒットした部品から減算されます。

この例をスタジオで実行するには:

  1. ServerScriptServiceに次のScriptを作成して、すべてのソリッドモデリング操作を実行します。

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local GeometryService = game:GetService("GeometryService")
    local DrawCurveEvent = ReplicatedStorage:WaitForChild("DrawCurveEvent")
    DrawCurveEvent.OnServerEvent:Connect(function(player, cframeList, hitInstance)
    local blade = Instance.new("Part")
    blade.Size = Vector3.new(0.2, 0.2, 15.0)
    local success, sweptPart = pcall(function()
    return GeometryService:SweepPartAsync(blade, cframeList)
    end)
    if success and sweptPart then
    -- スイープを視覚化
    sweptPart.Parent = workspace
    sweptPart.Transparency = 0.5
    sweptPart.Anchored = true
    sweptPart.CanQuery = false
    -- ヒットしたインスタンスからスイープを減算
    local subtractSuccess, newParts = pcall(function()
    return GeometryService:SubtractAsync(hitInstance, {sweptPart})
    end)
    if subtractSuccess and newParts then
    for _, newPart in pairs(newParts) do
    newPart.Parent = hitInstance.Parent
    newPart.Anchored = true
    end
    hitInstance:Destroy()
    end
    end
    end)
  2. StarterPlayerScriptsに次のLocalScriptを作成して、ユーザー入力を処理します。

    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    local GeometryService = game:GetService("GeometryService")
    local DrawCurveEvent = ReplicatedStorage:WaitForChild("DrawCurveEvent")
    DrawCurveEvent.OnServerEvent:Connect(function(player, cframeList, hitInstance)
    local blade = Instance.new("Part")
    blade.Size = Vector3.new(0.2, 0.2, 15.0)
    local success, sweptPart = pcall(function()
    return GeometryService:SweepPartAsync(blade, cframeList)
    end)
    if success and sweptPart then
    -- スイープを視覚化
    sweptPart.Parent = workspace
    sweptPart.Transparency = 0.5
    sweptPart.Anchored = true
    sweptPart.CanQuery = false
    -- ヒットしたインスタンスからスイープを減算
    local subtractSuccess, newParts = pcall(function()
    return GeometryService:SubtractAsync(hitInstance, {sweptPart})
    end)
    if subtractSuccess and newParts then
    for _, newPart in pairs(newParts) do
    newPart.Parent = hitInstance.Parent
    newPart.Anchored = true
    end
    hitInstance:Destroy()
    end
    end
    end)
  3. ReplicatedStorageDrawCurveEventという名前のRemoteEventを作成します。

フラグメント

FragmentAsync()およびGenerateFragmentSites()メソッドを使用すると、部品を自然な形状の破片に粉砕できます。FragmentAsync()ボロノイ分解を使用して、渡されたポイントのパターンに従って単一の部品を複数のMeshPartインスタンスに分割します。一方、GenerateFragmentSites()は、FragmentAsync()に渡すためのボロノイサイトとして知られるポイントを生成するヘルパーメソッドです。

次のコードサンプルは、ボロノイサイトを生成してブロック部品をフラグメント化します。

local GeometryService = game:GetService("GeometryService")
local inputPart = Instance.new("Part")
inputPart.Position = Vector3.new(0, 0.7, 20)
local sites = GeometryService:GenerateFragmentSites(inputPart)
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(inputPart, sites)
end)
if success and fragments then
for _, item in fragments do
local instance = item.Instance
instance.Parent = workspace
end
end
粉々になったブロック。
角が破片になったブロック。

次のスクリプトは、位置と半径によって与えられた部品の領域をフラグメント化します。この位置は、物理的な衝突やプレイヤーからのレイキャストから来ることが一般的です。

GenerateFragmentSites()が生成するサイト配列の最初の要素は、要求された半径の外側にあるすべてのサイトの内側配列になります。部品の残りの「未破壊部分」に特定の操作を行いたい場合は、FragmentAsync()の結果をループしているときにfragments[i].Index == 1をチェックすることでその部分を見つけることができます。

local GeometryService = game:GetService("GeometryService")
local function fragmentAtPosition(player, part, contactPoint, radius)
local allSites = GeometryService:GenerateFragmentSites(part, {Origin = contactPoint, Radius = radius})
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, allSites)
end)
if not success then
warn("破砕に失敗しました:"..tostring(fragments))
return
end
local decals = {}
for _,child in pairs(part:GetChildren()) do
if child:IsA("Decal") or child:IsA("SurfaceAppearance") then
table.insert(decals,child)
end
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
if fragment == nil then
continue
end
for _,d in pairs(decals) do
local d2 = d:Clone()
d2.Parent = fragment
end
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
end
if #fragments ~= 0 then
part:Destroy()
end
return fragments
end

次のスクリプトは、二つ目の部品を型紙として使用して、最初の部品から破片を取り除きます。二つ目の部品内のボロノイサイトのみが別々の破片を生成します。他のすべてのサイトは、そのセルが一つの部品に結合されます。

Roblox Studioロゴの形をした型紙
部品(濃い灰色)と型紙部品
型紙の形をしたフラグメントの結果
フラグメントスクリプトの結果
local GeometryService = game:GetService("GeometryService")
local function fragmentWithinStencil(player, part)
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.FilterDescendantsInstances = {workspace.Stencil}
overlapParams.RespectCanCollide = false
local sensor = Instance.new("Part")
sensor.Size = Vector3.new(0.01, 0.01, 0.01)
local allSites = GeometryService:GenerateFragmentSites(part, {SiteSpacing = 0.9})
local fragmentSites = {}
local mainPartSites = {}
for _, site in ipairs(allSites) do
sensor.CFrame = CFrame.new(site)
local partsFound = workspace:GetPartsInPart(sensor, overlapParams)
if #partsFound > 0 then
table.insert(fragmentSites, site)
else
table.insert(mainPartSites, site)
end
end
local sortedSites = fragmentSites
table.insert(sortedSites, mainPartSites)
workspace.Stencil:Destroy()
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, sortedSites, {SplitApart = false})
end)
if not success then
warn("破砕に失敗しました:"..tostring(fragments))
return
end
local decals = {}
for _,child in pairs(part:GetChildren()) do
if child:IsA("Decal") or child:IsA("SurfaceAppearance") then
table.insert(decals,child)
end
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
if fragment == nil then
continue
end
for _,d in pairs(decals) do
local d2 = d:Clone()
d2.Parent = fragment
end
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
end
if #fragments ~= 0 then
part:Destroy()
end
return fragments
end

次のスクリプトは、FragmentAsync()から返されるインデックスデータの力を示す、よりニッチな使用例です。

たとえば、多くの場所には、結合されていない複数のブロック部品で構成された建物があります。手榴弾、大砲、またはハンマーがそれを損傷した場合、すべての壁部品をフラグメント化したいでしょう。このスクリプトは、近くのすべての部品をフラグメント化し、異なる部品のフラグメントを結合して、継ぎ目を完全に隠します。

これは複数のAsync()操作を含むため、スレッドのユーザー入力に対する即時の応答としてゲーム内で使用するのには適していないかもしれません。

一列のブロック
一列のブロック
粉々になった一列のブロック
各破片は複数の入力部品から起こる可能性があります
local GeometryService = game:GetService("GeometryService")
local function fragmentCrossPart(player, part, contactPoint, radius)
local allSites = GeometryService:GenerateFragmentSites(part, {Origin = contactPoint, Radius = radius})
local fragmentsSorted = {}
for i = 1, #allSites do
fragmentsSorted[i] = {}
end
local partsFound = workspace:GetPartBoundsInRadius(contactPoint, radius)
for i, part in ipairs(partsFound) do
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, allSites)
end)
if not success then
warn("破砕に失敗しました:"..tostring(fragments))
return
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
local siteIndex = fragments[i].Index
if fragment == nil or siteIndex == nil then
continue
end
table.insert(fragmentsSorted[siteIndex], fragment)
end
end
for i = 1, #fragmentsSorted do
local fragmentList = fragmentsSorted[i]
if #fragmentList == 0 then
continue
end
if #fragmentList == 1 then
local fragment = fragmentList[1]
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
continue
end
if i == #allSites then
for j = 1, #fragmentList do
local fragment = fragmentList[j]
fragment.Parent = part.Parent
fragment.Anchored = true
end
continue
end
local mainPart = fragmentList[1]
local otherParts = {}
for j = 2, #fragmentList do
table.insert(otherParts, fragmentList[j])
end
local success, results = pcall(function()
return GeometryService:UnionAsync(mainPart, otherParts)
end)
if not success then
warn("結合に失敗しました:"..tostring(results))
return
end
for j = 1, #results do
results[j].Parent = part.Parent
results[j].Anchored = false
results[j]:SetNetworkOwner(player)
end
end
for i, part in ipairs(partsFound) do
part:Destroy()
end
end

次のスクリプトは、GenerateFragmentSites()のほぼ同じLuauの置き換えです。GenerateFragmentSites()と同様の動作を望むが、わずかな変更を加えたい場合は、これを出発点として使用できます。

これは、ジッターグリッドのポイントを使用し、フラグメント化された領域が適切に動作することを保証します。完全にランダムなポイントとは異なります。

local function generateFragmentSites(part: BasePart, siteSpacing: number?, origin: Vector3?, radius: number?): {Vector3}
local RANDOMNESS_MULTIPLIER = 1.0 -- ジッターの量を調整するために使用します
if (origin and not radius) or (radius and not origin) then
warn("原点と半径の両方を提供するか、どちらも提供しない必要があります。")
return {}
end
local isLocalized = (radius ~= nil) -- isLocalizedは、全体の部品を破壊するのではなく、セクションだけを破壊することを意味します。
local partCFrame = part.ExtentsCFrame
local gridDimensions: Vector3
local localGridCenter: Vector3
local spacing
if siteSpacing then
spacing = siteSpacing
elseif isLocalized then
spacing = radius * 0.5
else
local partSize = part.Size
local volume = partSize.X * partSize.Y * partSize.Z
spacing = (volume / 5) ^ (1/3)
end
if isLocalized then
local localOrigin = partCFrame:PointToObjectSpace(origin)
local gridSize = math.ceil(radius * 2 / spacing) + 3
gridDimensions = Vector3.new(gridSize, gridSize, gridSize)
localGridCenter = localOrigin
else
local partSize: Vector3 = part.Size
local xCount = math.ceil(partSize.X / spacing)
local yCount = math.ceil(partSize.Y / spacing)
local zCount = math.ceil(partSize.Z / spacing)
gridDimensions = Vector3.new(xCount, yCount, zCount)
localGridCenter = Vector3.zero
end
local totalGridSize = gridDimensions * spacing
local halfCell = Vector3.new(spacing, spacing, spacing) * 0.5
local localStartOffset = localGridCenter - (totalGridSize * 0.5) + halfCell
local innerJitter = spacing * 0.5 * RANDOMNESS_MULTIPLIER
local outerJitter = math.min(spacing * 0.5 * 0.866, innerJitter)
local sitesFlatList = {}
for x = 0, gridDimensions.X - 1 do
for y = 0, gridDimensions.Y - 1 do
for z = 0, gridDimensions.Z - 1 do
local isOuterShell =
x == 0 or x == gridDimensions.X - 1 or
y == 0 or y == gridDimensions.Y - 1 or
z == 0 or z == gridDimensions.Z - 1
local jitterAmount = if (isOuterShell and isLocalized) then outerJitter else innerJitter
local jitterOffset = Vector3.new(
(math.random() - 0.5) * 2 * jitterAmount,
(math.random() - 0.5) * 2 * jitterAmount,
(math.random() - 0.5) * 2 * jitterAmount
)
local offsetInGrid = Vector3.new(x, y, z) * spacing
table.insert(sitesFlatList, localStartOffset + offsetInGrid + jitterOffset)
end
end
end
local sitesListFinal = {}
if isLocalized then
local mainPartSites = {}
for _, localSite in ipairs(sitesFlatList) do
local worldSite = partCFrame * localSite
local distance = (worldSite - origin).Magnitude
if distance < radius then
table.insert(sitesListFinal, worldSite)
else
table.insert(mainPartSites, worldSite)
end
end
table.insert(sitesListFinal, 1, mainPartSites)
else
for _, localSite in ipairs(sitesFlatList) do
local worldSite = partCFrame * localSite
table.insert(sitesListFinal, worldSite)
end
end
return sitesListFinal
end

制約の保持

入力部品に保持したい制約やアタッチメントがある場合、それらを結果の部品に転送できます。どの出力部品に制約を取り付けるべきかを判断するのは面倒な場合があるため、CalculateConstraintsToPreserve()を使用して、ループして適用できる推奨のテーブルを生成することをお勧めします。

次のコードサンプルは、減算操作を実行し、結果の部品を再親子化および再配置するためにループし、元の部品を破壊する前に保持または削除する制約とアタッチメントのテーブルを計算します。

local GeometryService = game:GetService("GeometryService")
local mainPart = workspace.PurpleBlock
local otherParts = { workspace.BlueBlock }
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = true,
}
local constraintOptions = {
tolerance = 0.1,
weldConstraintPreserve = Enum.WeldConstraintPreserve.All,
dropAttachmentsWithoutConstraints = false,
}
-- pcall()内で減算操作を実行します。非同期のため
local success, newParts = pcall(function()
return GeometryService:SubtractAsync(mainPart, otherParts, options)
end)
if success and newParts then
-- 結果の部品を再親子化/再配置するためにループ
for _, newPart in pairs(newParts) do
newPart.Parent = mainPart.Parent
newPart.CFrame = mainPart.CFrame
newPart.Anchored = mainPart.Anchored
end
-- 保持または削除する制約/アタッチメントを計算
local recommendedTable = GeometryService:CalculateConstraintsToPreserve(mainPart, newParts, constraintOptions)
-- 推奨テーブルに基づいて制約/アタッチメントを保持
for _, item in pairs(recommendedTable) do
if item.Attachment then
item.Attachment.Parent = item.AttachmentParent
if item.Constraint then
item.Constraint.Parent = item.ConstraintParent
end
elseif item.NoCollisionConstraint then
local newNoCollision = Instance.new("NoCollisionConstraint")
newNoCollision.Part0 = item.NoCollisionPart0
newNoCollision.Part1 = item.NoCollisionPart1
newNoCollision.Parent = item.NoCollisionParent
elseif item.WeldConstraint then
local newWeldConstraint = Instance.new("WeldConstraint")
newWeldConstraint.Part0 = item.WeldConstraintPart0
newWeldConstraint.Part1 = item.WeldConstraintPart1
newWeldConstraint.Parent = item.WeldConstraintParent
end
end
-- 元の部品を破壊
mainPart.Parent = nil
mainPart:Destroy()
for _, otherPart in pairs(otherParts) do
otherPart.Parent = nil
otherPart:Destroy()
end
end

ソリッドモデリングの結果

挙動の詳細

  • 元の部品は、操作が成功した後もそのまま残り、返された部品には設定されたParentがありません。ほとんどの場合、返された部品をメイン部品と同じ場所に親子化し、元の部品をDestroy()することをお勧めします。

  • メイン部品が操作の計算中に移動している場合、返された部品をメイン部品の更新されたCFrameに設定できます。返された部品はメイン部品と同じ座標空間にあるためです。

  • ソリッドモデリング操作の結果、20,000三角形を超える部品が生成される場合、それらは20,000に簡略化されます。これができない場合、通常は数千の非重複コンポーネントがある場合にエラーが発生します。

  • インスタンスのメッシュデータを入れ替えるメソッドがあり、メイン部品のプロパティ、属性、タグ、および子を維持しながら操作のジオメトリを利用しやすくします。これにより、元のPartOperationを別のもので完全に置き換える際の「ちらつき」を回避できます。

  • クライアントでこれらのメソッドを呼び出すことは可能ですが、いくつかの制限があります。まず、クライアントで作成されたオブジェクトで行う必要があります。第二に、クライアントからサーバーへのレプリケーションは利用できません。

色とUV

ソリッドモデリング後の結果の部品の色は、二つの場所から来ます:面の色と部品のColor

  • 結果がPartOperationの場合、スタジオで最初に選択した部品のColorを持ちますが、スタジオはデフォルトで面の色を使用して、各面を操作前と同じ色に保ちます。この動作をオーバーライドして結果全体を単一の色にするには、スタジオでそのUsePartColorプロパティを有効にできます。
  • 結果がMeshPartの場合、そのColorは白であり、面の色は常に透けて見えます。結果の部品の色合いを調整するには、Colorを変更しますが、面の色と混ざ(乗算)されます。これにより、結果が色合いを持つようになりますが、面の色を完全にオーバーライドすることはありません。出力の色を完全に制御したい場合は、入力を最初に白にするのが最善です。

UVも結果のタイプによって異なります:

  • PartOperationsは常にボックスマッピングされたUVを持ちます。これは、各面に一方向(-x+x-y+y-z+zのいずれか)のマテリアル/テクスチャ/デカールが適用されることを意味します。これにより、テクスチャが引き伸ばされることがあります。
  • MeshPartsはボックスマッピングされていません。メイン部品のメッシュのUVが使用されます。Robloxは現在マルチマテリアルサポートを持っていないため、他の部品から起こる面のUVは(0, 0)のUVが与えられます。最良の結果を得るためには、テクスチャのピクセル(0, 0)が適切な色を持っていることを確認してください。

スムージング角度

ソリッドモデリングされた部品のSmoothingAngleプロパティは、同じ色の隣接する表面間の角度を滑らかにします。値が高いほど滑らかな外観になり、値が低いほど、より鋭いエッジを持つ粗い外観になります。

30度から70度の値は通常良好な結果を生み出しますが、90度から180度の値は推奨されません。なぜなら、鋭いエッジを持つ和や交差に「影の効果」を引き起こす可能性があるからです。

©2026 Roblox Corporation。Roblox(ロブロックス)、RobloxロゴおよびPowering Imaginationは、米国並びにその他の国における登録商標および非登録商標です。