mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-24 06:34:09 +00:00
update rules
This commit is contained in:
parent
4c390fdd37
commit
d5522b2fef
8549 changed files with 11783 additions and 1888 deletions
148
.cursor/rules/roblox/en-us/workspace/camera.md
Normal file
148
.cursor/rules/roblox/en-us/workspace/camera.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
---
|
||||
title: Customize the camera
|
||||
description: Explains how to configure an experience's camera settings.
|
||||
---
|
||||
|
||||
Roblox's built-in camera powers a default **third person** mode and an optional **first person** mode, so you don't need to build your own following camera. For more customized scenarios, you can adjust the default properties in `Class.Camera` or replace it entirely like for over-the-shoulder, isometric, and weapon scoping views.
|
||||
|
||||
## Basic settings
|
||||
|
||||
You can configure common camera settings directly within Studio's `Class.StarterPlayer` object. These settings include [zoom distance](#zoom-distance) and various [camera](#camera-mode), [occlusion](#occlusion-mode), and [movement](#movement-mode) modes.
|
||||
|
||||
1. In the **Explorer** window, select the **StarterPlayer** object.
|
||||
|
||||
<img src="../assets/studio/explorer/StarterPlayer.png" width="320" alt="StarterPlayer in Explorer" />
|
||||
|
||||
1. In the **Properties** window, scroll down to locate the **Camera** section. You can configure the following properties directly or through a script.
|
||||
|
||||
<img src="../assets/studio/properties/StarterPlayer-Camera-Group.png" width="320" alt="Camera properties on StarterPlayer in Properties Window" />
|
||||
|
||||
### Zoom distance
|
||||
|
||||
Together, `Class.StarterPlayer.CameraMaxZoomDistance|CameraMaxZoomDistance` and `Class.StarterPlayer.CameraMinZoomDistance|CameraMinZoomDistance` set the range in which players can zoom the camera in respect to their player character. Setting a very high maximum such as 500 allows players to zoom the camera far out in space. If you want to lock the camera to a specific distance away from the character and prevent zooming, set both of these properties to the same value.
|
||||
|
||||
```lua title="LocalScript - Camera Zoom Range" highlight="5,6"
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local player = Players.LocalPlayer
|
||||
|
||||
player.CameraMaxZoomDistance = 25
|
||||
player.CameraMinZoomDistance = 50
|
||||
```
|
||||
|
||||
### Camera mode
|
||||
|
||||
The `Class.StarterPlayer.CameraMode|CameraMode` property sets the overall behavior of the camera between two options:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Classic**</td>
|
||||
<td>The classic Roblox third-person camera which can be zoomed into first-person. Allows players to zoom in and out (unless zoom is locked) and rotate the camera around their character.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**LockFirstPerson**</td>
|
||||
<td>Locks the camera to first-person mode. When in this mode, all parts/elements of the player's character are invisible to them, except for equipped `Class.Tool|Tools`.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Occlusion mode
|
||||
|
||||
The `Class.StarterPlayer.DevCameraOcclusionMode|DevCameraOcclusionMode` property controls camera behavior when the player cannot see their character, such as when it's obscured by a `Class.BasePart`.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Zoom**</td>
|
||||
<td>If the player's character moves behind an object with `Class.BasePart.Transparency|Transparency` lower than 0.25, the camera zooms in very close to the character so that it can be seen. Once the character moves back into a viewable position, the camera zooms back out.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Invisicam**</td>
|
||||
<td>If the player's character moves behind an object with `Class.BasePart.Transparency|Transparency` lower than 0.75, the camera remains unmoved but the object becomes semi-transparent so that the character can be seen. Once the character moves back into a viewable position, the object returns to its normal opacity.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<figure>
|
||||
<video controls src="../assets/workspace/camera/Camera-Occlusion.mp4" width="90%" alt="Camera occlusion mode: Zoom vs. Invisicam"></video>
|
||||
<figcaption>**Zoom** and **Invisicam** occlusion modes</figcaption>
|
||||
</figure>
|
||||
|
||||
### Movement mode
|
||||
|
||||
The `Class.StarterPlayer.DevComputerCameraMovementMode|DevComputerCameraMovementMode` (computer) and `Class.StarterPlayer.DevTouchCameraMovementMode|DevTouchCameraMovementMode` (phone/tablet) determine how the player can move the camera around.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**UserChoice**</td>
|
||||
<td>The camera will move based on the player's in-experience camera settings.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Classic**</td>
|
||||
<td>The camera remains at its [zoom distance](#zoom-distance), tracking the player's character as it moves around the world. Players can also pitch the camera view up/down and orbit it around their character.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Follow**</td>
|
||||
<td>Similar to **Classic** but the camera may rotate slightly to face the player's character if they're moving in any direction that isn't parallel to the camera's facing direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Orbital**</td>
|
||||
<td>The camera remains at a fixed zoom distance and tracks the player's character as it moves around the world. Players can orbit the camera around their character but can't pitch the view up or down.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**CameraToggle**</td>
|
||||
<td>Only functional on computers (not phones/tablets) through `Class.StarterPlayer.DevComputerCameraMovementMode|DevComputerCameraMovementMode`. When the player clicks the right mouse button, the camera toggles between **Classic** mode and a "free look" mode where moving the mouse looks around the world.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Script the camera
|
||||
|
||||
Each player [client](../projects/client-server.md) has its own `Class.Camera` object residing in the local `Class.Workspace`, accessible through the `Class.Workspace.CurrentCamera` property. You can override Roblox's default camera scripts by setting its `Class.Camera.CameraType|CameraType` to `Enum.CameraType|Scriptable` and then, most commonly, control the camera through the following properties.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Camera.CFrame`</td>
|
||||
<td>The `Datatype.CFrame` of the camera. This is the most frequently used property for positioning and orienting a `Enum.CameraType|Scriptable` camera in an experience.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Camera.FieldOfView`</td>
|
||||
<td>The extent of the observable 3D space that can be seen on screen, measured between 1–120 degrees in the direction defined by `Class.Camera.FieldOfViewMode`. Default is 70.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Camera.CameraType`</td>
|
||||
<td>Toggles between the various camera behaviors outlined in `Enum.CameraType`, some of which mimic the selectable [movement modes](#movement-mode). Setting this to `Enum.CameraType|Scriptable` gives you full control of the camera.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Camera.Focus`</td>
|
||||
<td>The point in 3D space where the camera is looking. If you've set `Class.Camera.CameraType` to `Enum.CameraType|Scriptable`, you should update this property every frame because certain visuals are more detailed depending on how close they are to the focus point.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
264
.cursor/rules/roblox/en-us/workspace/cframes.md
Normal file
264
.cursor/rules/roblox/en-us/workspace/cframes.md
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
title: CFrames
|
||||
description: CFrames, or Coordinate Frames, are a data type that you can use to rotate and position objects in the 3D space.
|
||||
---
|
||||
|
||||
A `Datatype.CFrame`, short for **Coordinate Frame**, is a data type used to rotate and position 3D objects. As either an object property or a standalone unit, a `Datatype.CFrame` contains global **X**, **Y**, and **Z** coordinates as well as rotation data for each axis. In addition, `Datatype.CFrame|CFrames` contain helpful functions for working with objects in the 3D space.
|
||||
|
||||
## Position a CFrame
|
||||
|
||||
You can create an empty `Datatype.CFrame` at the default position of <Typography noWrap>`(0, 0, 0)`</Typography> by using `Datatype.CFrame.new()`. To position a `Datatype.CFrame` at a specific point, provide **X**, **Y**, and **Z** coordinates as arguments to `Datatype.CFrame.new()`. In the following example, the `redBlock` part's `Datatype.CFrame` property changes to `newCFrame`, repositioning it to <Typography noWrap>`(-2, 2, 4)`</Typography>.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
|
||||
-- Create new CFrame
|
||||
local newCFrame = CFrame.new(-2, 2, 4)
|
||||
|
||||
-- Overwrite the red block's current CFrame with the new CFrame
|
||||
redBlock.CFrame = newCFrame
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Strict-Position-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Strict-Position-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Alternatively, you can provide a new `Datatype.Vector3` position to `Datatype.CFrame.new()` and achieve the same result:
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
|
||||
-- Create new CFrame
|
||||
local newVector3 = Vector3.new(-2, 2, 4)
|
||||
local newCFrame = CFrame.new(newVector3)
|
||||
|
||||
-- Overwrite the red block's current CFrame with the new CFrame
|
||||
redBlock.CFrame = newCFrame
|
||||
```
|
||||
|
||||
## Rotate a CFrame
|
||||
|
||||
To create a rotated `Datatype.CFrame`, use the `Datatype.CFrame.fromEulerAnglesXYZ()` constructor, providing a rotation angle in radians for the desired axes. The parameters to `Datatype.CFrame.fromEulerAnglesXYZ()` are in radians, not degrees; if you prefer degrees, use `Library.math.rad()` to convert degrees to radians. In the following example, the `redBlock` part rotates 45 degrees counterclockwise on its **Y** axis.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
|
||||
-- Create new rotated CFrame
|
||||
local newCFrame = CFrame.fromEulerAnglesXYZ(0, math.rad(45), 0)
|
||||
|
||||
-- Overwrite the red block's current CFrame with the new CFrame
|
||||
redBlock.CFrame = newCFrame
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Strict-Rotation-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Strict-Rotation-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Face a CFrame toward a point
|
||||
|
||||
You can use `Datatype.CFrame.lookAt()` to point the front surface of a `Datatype.CFrame` at a specific point in the world. The following example positions `redBlock` at <Typography noWrap>`(0, 3, 0)`</Typography> and points its front surface, marked by the white circle, at the `blueCube` part.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
local blueCube = Workspace.BlueCube
|
||||
|
||||
redBlock.Position = Vector3.new(0, 3, 0)
|
||||
|
||||
-- Point the front surface of the red block at the blue cube
|
||||
redBlock.CFrame = CFrame.lookAt(redBlock.Position, blueCube.Position)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Front-Face-Pointing-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Front-Face-Pointing-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Offset a CFrame
|
||||
|
||||
To offset an object by a specific number of studs from its current position, add or subtract a `Datatype.Vector3` to or from a new `Datatype.CFrame` at the object's position. To get a properly-formatted `Datatype.Vector3` position of an object to use with `Datatype.CFrame.new()`, as seen here, its `Class.BasePart.Position|Position` property (`redBlock.Position`) is a convenient shortcut.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
|
||||
redBlock.CFrame = CFrame.new(redBlock.Position) + Vector3.new(0, 1.25, 0)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Self-Offset-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Self-Offset-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
You can use the same technique to offset an object from the position of another object. In the following example, a `Datatype.Vector3` adds to a new `Datatype.CFrame` created at the blue cube's position instead of the block's position.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
local blueCube = Workspace.BlueCube
|
||||
|
||||
redBlock.CFrame = CFrame.new(blueCube.Position) + Vector3.new(0, 2, 0)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Other-Part-Offset-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Other-Part-Offset-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Dynamic CFrame orientation
|
||||
|
||||
The `Datatype.CFrame.new()` and `Datatype.CFrame.fromEulerAnglesXYZ()` constructors reposition or rotate an object at a specific orientation within the world, but you sometimes can't rely on a fixed world position and rotation angle. For example:
|
||||
|
||||
- Placing a floating treasure directly in front of a player who may be standing anywhere in the world, facing any direction.
|
||||
- Making a magical genie appear directly above a player's right shoulder.
|
||||
|
||||
In these cases, use `Datatype.CFrame` methods instead of their constructors.
|
||||
|
||||
### Relative position
|
||||
|
||||
The `Datatype.CFrame:ToWorldSpace()` function transforms an object's `Datatype.CFrame` — respecting its own local orientation — to a new **world** orientation. This makes it ideal for offsetting a part relative to itself or another object, regardless of how it's currently positioned/rotated.
|
||||
|
||||
In the following example, the `redBlock` part offsets 2 studs relative to the **Y** axis of the blue cube (the green arrow pointing through it) and **not** relative to the global **Y** axis pointing straight up.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
local blueCube = Workspace.BlueCube
|
||||
|
||||
local offsetCFrame = CFrame.new(0, 2, 0)
|
||||
redBlock.CFrame = blueCube.CFrame:ToWorldSpace(offsetCFrame)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Other-Part-Relative-Position-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Other-Part-Relative-Position-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Relative rotation
|
||||
|
||||
You can also use `Datatype.CFrame:ToWorldSpace()` to rotate an object relative to itself. In the following example, the `redBlock` part rotates 70 degrees counterclockwise on its **Y** axis and 20 degrees clockwise on its **Z** axis.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
|
||||
local rotatedCFrame = CFrame.fromEulerAnglesXYZ(0, math.rad(70), math.rad(20))
|
||||
redBlock.CFrame = redBlock.CFrame:ToWorldSpace(rotatedCFrame)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Self-Rotate-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Self-Rotate-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Face a specific surface toward a point
|
||||
|
||||
You can make the front of an object face another object by supplying a `Datatype.Vector3` point as the second parameter of `Datatype.CFrame.new()`. You can also use relative rotation to make any face of the object point toward a `Datatype.Vector3` point. The following example performs two consecutive `Datatype.CFrame` operations:
|
||||
|
||||
1. Point the **front** surface, marked by the white circle, at the target.
|
||||
2. Rotate the `Datatype.CFrame` to make the **top** surface, marked by the black circle, point toward the target.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
local blueCube = Workspace.BlueCube
|
||||
|
||||
-- Point the red block's front surface at the blue cube
|
||||
redBlock.CFrame = CFrame.lookAt(redBlock.Position, blueCube.Position)
|
||||
|
||||
-- Rotate CFrame relative to itself so that top surface (not front) points toward blue cube
|
||||
local rotatedCFrame = CFrame.fromEulerAnglesXYZ(math.rad(-90), 0, 0)
|
||||
redBlock.CFrame = redBlock.CFrame:ToWorldSpace(rotatedCFrame)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Top-Face-Pointing-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Top-Face-Pointing-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Find a point between points
|
||||
|
||||
You can use **linear interpolation**, or **lerp**, to position a `Datatype.CFrame` between two points. In the following example, the `redBlock` part repositions between the `greenCube` and `cyanCube` parts. The value of `0.7` places it 70% of the distance away from the green cube.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local redBlock = Workspace.RedBlock
|
||||
local greenCube = Workspace.GreenCube
|
||||
local cyanCube = Workspace.CyanCube
|
||||
|
||||
redBlock.CFrame = greenCube.CFrame:Lerp(cyanCube.CFrame, 0.7)
|
||||
```
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Lerp-A.png" />
|
||||
<figcaption>Before</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/cframes/CFrame-Lerp-B.png" />
|
||||
<figcaption>After</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
369
.cursor/rules/roblox/en-us/workspace/collisions.md
Normal file
369
.cursor/rules/roblox/en-us/workspace/collisions.md
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
---
|
||||
title: Collisions
|
||||
description: Explains methods to detect physical collisions, handle collision events, and fine-tune which objects collide with others.
|
||||
---
|
||||
|
||||
A collision occurs when two 3D objects come into contact within the 3D world. For customized collision handling, `Class.BasePart` has a set of [collision events](#collision-events) and [collision filtering](#collision-filtering) techniques, so you can control which physical assemblies collide with others.
|
||||
|
||||
## Collision events
|
||||
|
||||
Collision **events** occur when two `Class.BasePart|BaseParts` touch or stop touching in the 3D world. You can detect these collisions through the `Class.BasePart.Touched|Touched` and `Class.BasePart.TouchEnded|TouchEnded` events which occur regardless of either part's `Class.BasePart.CanCollide|CanCollide` property value. When considering collision handling on parts, note the following:
|
||||
|
||||
- A part's `Class.BasePart.CanTouch|CanTouch` property determines whether it triggers collision events. If set to `false`, neither `Class.BasePart.Touched|Touched` nor `Class.BasePart.TouchEnded|TouchEnded` will fire.
|
||||
- A part's `Class.BasePart.CanCollide|CanCollide` property affects whether it will **physically** collide with other parts and cause forces to act upon them. Even if `Class.BasePart.CanCollide|CanCollide` is disabled for a part, you can detect touch and non‑touch through `Class.BasePart.Touched|Touched` and `Class.BasePart.TouchEnded|TouchEnded` events.
|
||||
- The `Class.BasePart.Touched|Touched` and `Class.BasePart.TouchEnded|TouchEnded` events only fire as a result of **physical** movement, not from a `Class.BasePart.Position|Position` or `Class.BasePart.CFrame|CFrame` changes that cause a part to intersect or stop intersecting another part.
|
||||
- The top-level `Class.Terrain` class inherits from `Class.BasePart`, so you can assign a [collision group](#collision-groups) to `Class.Terrain` to determine whether other `Class.BasePart|BaseParts` collide with [Terrain](../parts/terrain.md) voxels.
|
||||
|
||||
<Alert severity="info">
|
||||
For performance optimization, set `Class.BasePart.CanTouch|CanTouch` to `false` for objects that don't require collisions.
|
||||
</Alert>
|
||||
|
||||
### Touched
|
||||
|
||||
The `Class.BasePart.Touched|Touched` event fires when a `Class.BasePart`
|
||||
comes in contact with another, or with a [Terrain](../parts/terrain.md) voxel. It only fires as a result of **physical simulation** and will not fire when the part's `Class.BasePart.Position|Position` or `Class.BasePart.CFrame|CFrame` is explicitly set such that it intersects another part or voxel.
|
||||
|
||||
The following code pattern shows how the `Class.BasePart.Touched|Touched` event can be connected to a custom `onTouched()` function. Note that the event sends the `otherPart` argument to the function, indicating the other part involved in the collision.
|
||||
|
||||
```lua title="Part Collision" highlight="3-5, 7"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local part = Workspace.Part
|
||||
|
||||
local function onTouched(otherPart)
|
||||
print(part.Name .. " collided with " .. otherPart.Name)
|
||||
end
|
||||
|
||||
part.Touched:Connect(onTouched)
|
||||
```
|
||||
|
||||
Note that the `Class.BasePart.Touched|Touched` event can fire multiple times in quick succession based on subtle physical collisions, such as when a moving object "settles" into a resting position or when a collision involves a [multi‑part model](#model-collisions). To avoid triggering more `Class.BasePart.Touched|Touched` events than necessary, you can implement a simple debounce system which enforces a "cooldown" period through an instance [attribute](../studio/properties.md#instance-attributes).
|
||||
|
||||
```lua title="Part Collision With Cooldown" highlight="3, 6, 9-12"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local part = Workspace.Part
|
||||
|
||||
local COOLDOWN_TIME = 1
|
||||
|
||||
local function onTouched(otherPart)
|
||||
if not part:GetAttribute("Touched") then
|
||||
print(part.Name .. " collided with " .. otherPart.Name)
|
||||
|
||||
part:SetAttribute("Touched", true) -- Set attribute to true
|
||||
task.wait(COOLDOWN_TIME) -- Wait for cooldown duration
|
||||
part:SetAttribute("Touched", false) -- Reset attribute
|
||||
end
|
||||
end
|
||||
|
||||
part.Touched:Connect(onTouched)
|
||||
```
|
||||
|
||||
### TouchEnded
|
||||
|
||||
The `Class.BasePart.TouchEnded|TouchEnded` event fires when the entire collision bounds of a `Class.BasePart` exits the bounds of another `Class.BasePart` or a filled [Terrain](../parts/terrain.md) voxel. It only fires as a result of **physical simulation** and will not fire when the part's `Class.BasePart.Position|Position` or `Class.BasePart.CFrame|CFrame` is explicitly set such that it stops intersecting another part or voxel.
|
||||
|
||||
The following code pattern shows how the `Class.BasePart.TouchEnded|TouchEnded` event can be connected to a custom `onTouchEnded()` function. Like `Class.BasePart.Touched|Touched`, the event sends the `otherPart` argument to the function, indicating the other part involved.
|
||||
|
||||
```lua title="Non-Collision Detection" highlight="3-5, 7"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local part = Workspace.Part
|
||||
|
||||
local function onTouchEnded(otherPart)
|
||||
print(part.Name .. " is no longer touching " .. otherPart.Name)
|
||||
end
|
||||
|
||||
part.TouchEnded:Connect(onTouchEnded)
|
||||
```
|
||||
|
||||
## Collision filtering
|
||||
|
||||
Collision **filtering** defines which physical parts collide with others. You can configure filtering for numerous objects through [collision groups](#collision-groups) or you can control collisions on a [part‑to‑part](#part-to-part-filtering) basis with `Class.NoCollisionConstraint` instances.
|
||||
|
||||
### Collision groups
|
||||
|
||||
Collision **groups** let you assign `Class.BasePart|BaseParts` to dedicated groups and specify whether or not they collide with those in other groups. Parts within non‑colliding groups pass through each other completely, even if both parts have their `Class.BasePart.CanCollide|CanCollide` property set to `true`.
|
||||
|
||||
<figure>
|
||||
<video src="../assets/physics/collisions/Collision-Groups.mp4" controls width="100%" alt="Video of spinning objects in different collision groups colliding or not colliding"></video>
|
||||
<figcaption>In the video above, the spinning objects are in different collision groups such that they collide with objects of another color but not with objects of their own color</figcaption>
|
||||
</figure>
|
||||
|
||||
You can easily set up collision groups through Studio's **Collision Groups** editor, accessible through Studio's **Window** ⟩ **3D** menu.
|
||||
|
||||
The editor functions in either **List View** which favors [docking](../studio/ui-overview.md#reposition-windows) to the left or right side of Studio, or in a wider **Table View**, which favors docking to the top or bottom.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="List View">
|
||||
<img src="../assets/studio/collision-groups-editor/List-View.png" width="500" height="176" alt="List View example in Collision Groups Editor" />
|
||||
</TabItem>
|
||||
<TabItem label="Table View">
|
||||
<img src="../assets/studio/collision-groups-editor/Table-View.png" width="500" height="176" alt="Table View example in Collision Groups Editor" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Register groups
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Studio Editor">
|
||||
The editor includes one **Default** collision group which cannot be renamed or deleted. All `Class.BasePart|BaseParts` automatically belong to this default group unless assigned to another group, meaning that they will collide with all other objects in the **Default** group.
|
||||
|
||||
To create a new collision group:
|
||||
|
||||
1. Click the **Add Group** button along the top of the editor panel, enter a new group name, and press <kbd>Enter</kbd>. The new group appears in both columns of list view, or in both the left column and upper row of table view.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="List View">
|
||||
<img src="../assets/studio/collision-groups-editor/New-Group-List-View.png" width="500" height="220" alt="New group added to Collision Groups Editor in List View" />
|
||||
</TabItem>
|
||||
<TabItem label="Table View">
|
||||
<img src="../assets/studio/collision-groups-editor/New-Group-Table-View.png" width="500" height="220" alt="New group added to Collision Groups Editor in Table View" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
1. Repeat the process if necessary, choosing a unique and descriptive name for each group. Note that you can change a group's name during development by clicking in its field, or by selecting it and clicking the **rename** button.
|
||||
|
||||
<img src="../assets/studio/collision-groups-editor/Rename-Group.png" width="500" alt="Button and field indicated for renaming a group in the Collision Groups Editor" />
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Scripting">
|
||||
To create a new collision group through scripting, include the `Class.PhysicsService` service and register the group with `Class.PhysicsService:RegisterCollisionGroup()`. It may be helpful to pre-declare your group names in local variables, as the same strings can be used for [assigning objects](#assign-objects-to-groups) and [configuring groups](#configure-group-collisions) within the same script.
|
||||
|
||||
```lua title="Collision Group Setup" highlight="1,3,4,7,8"
|
||||
local PhysicsService = game:GetService("PhysicsService")
|
||||
|
||||
local cubes = "Cubes"
|
||||
local doors = "Doors"
|
||||
|
||||
-- Register two collision groups
|
||||
PhysicsService:RegisterCollisionGroup(cubes)
|
||||
PhysicsService:RegisterCollisionGroup(doors)
|
||||
```
|
||||
|
||||
<Alert severity="warning">
|
||||
Since scripts are not guaranteed to execute in any particular order, it's highly recommended that you register collision groups in a single script. Abstracting group registration among multiple scripts may result in a race condition where a group is not yet registered at the time you [configure groups](#configure-group-collisions) or [assign objects](#assign-objects-to-groups) to them.
|
||||
</Alert>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Configure group collisions
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Studio Editor">
|
||||
Under default configuration, objects in all groups collide with each other. To prevent objects in one group from colliding with objects in another group, **uncheck** the box in the respective row/column.
|
||||
|
||||
In the following example, objects in the **Cubes** group will **not** collide with objects in the **Doors** group.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="List View">
|
||||
<img src="../assets/studio/collision-groups-editor/Configure-Groups-List-View.png" width="500" height="176" alt="Group configured in List View of Collision Groups Editor" />
|
||||
</TabItem>
|
||||
<TabItem label="Table View">
|
||||
<img src="../assets/studio/collision-groups-editor/Configure-Groups-Table-View.png" width="500" height="176" alt="Group configured in Table View of Collision Groups Editor" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label="Scripting">
|
||||
To configure how objects in two collision groups interact, call `Class.PhysicsService:CollisionGroupSetCollidable()|CollisionGroupSetCollidable()`, providing the two collision groups and a boolean `true` (collidable) or `false` (non‑collidable). If objects in the same group should or shouldn't collide with each other, use that group name for both the first and second parameters.
|
||||
|
||||
```lua title="Collision Group Setup" highlight="15"
|
||||
local PhysicsService = game:GetService("PhysicsService")
|
||||
|
||||
local cubes = "Cubes"
|
||||
local doors = "Doors"
|
||||
|
||||
-- Register two collision groups
|
||||
PhysicsService:RegisterCollisionGroup(cubes)
|
||||
PhysicsService:RegisterCollisionGroup(doors)
|
||||
|
||||
-- Set cubes to be non-collidable with doors
|
||||
PhysicsService:CollisionGroupSetCollidable(cubes, doors, false)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Assign objects to groups
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Studio Editor">
|
||||
To assign objects to groups you've [registered](#register-groups) through the Studio editor:
|
||||
|
||||
1. Select one or more `Class.BasePart|BaseParts` that qualify as part of a collision group.
|
||||
2. Assign them to the group by clicking the **⊕** button for its row. Objects can belong to only one collision group at a time, so placing them in a new group removes them from their current group.
|
||||
|
||||
<img src="../assets/studio/collision-groups-editor/Add-To-Group.png" width="500" alt="Plus button indicated in Collision Groups Editor for adding selected parts to a group" />
|
||||
|
||||
Once assigned, the new group is reflected under the object's `Class.BasePart.CollisionGroup|CollisionGroup` property.
|
||||
|
||||
<img src="../assets/physics/collisions/BasePart-CollisionGroup.png" width="320" alt="Chosen collision group indicated as the part's CollisionGroup property" />
|
||||
</TabItem>
|
||||
<TabItem label="Scripting">
|
||||
To add a `Class.BasePart` to a collision group through scripting, simply assign the group's **string name**, previously registered through `Class.PhysicsService:RegisterCollisionGroup()|RegisterCollisionGroup()`, to the part's `Class.BasePart.CollisionGroup|CollisionGroup` property.
|
||||
|
||||
```lua title="Collision Group Setup" highlight="7,8,11,12"
|
||||
local PhysicsService = game:GetService("PhysicsService")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local cubes = "Cubes"
|
||||
local doors = "Doors"
|
||||
|
||||
-- Register two collision groups
|
||||
PhysicsService:RegisterCollisionGroup(cubes)
|
||||
PhysicsService:RegisterCollisionGroup(doors)
|
||||
|
||||
-- Set cubes to be non-collidable with doors
|
||||
PhysicsService:CollisionGroupSetCollidable(cubes, doors, false)
|
||||
|
||||
-- Assign an object to each group
|
||||
Workspace.Cube1.CollisionGroup = cubes
|
||||
Workspace.Door1.CollisionGroup = doors
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### StudioSelectable collision group
|
||||
|
||||
Tools in Studio use the collision filtering system to determine which objects are candidates for selection when clicking in the 3D viewport. Objects whose assigned collision group does **not** collide with **StudioSelectable** will be ignored.
|
||||
|
||||
For example, if you have checkpoints in a racing experience whose effective areas are defined by large transparent parts, you can assign them to a **Checkpoints** collision group and then make that group non‑collidable with **StudioSelectable** so that they don't get in the way when you're editing the underlying map geometry.
|
||||
|
||||
<img src="../assets/studio/collision-groups-editor/StudioSelectable-Off.png" width="500" alt="Checkpoints group configured to be non-collidable with StudioSelectable group" />
|
||||
|
||||
For plugin code, it's recommended that you assign `"StudioSelectable"` as the collision group filter of your `Datatype.RaycastParams` when finding parts under the cursor. This allows your plugins to match the selection mechanics that creators have learned to expect from built‑in Studio tools.
|
||||
|
||||
```lua title="Recommended Plugin Selection Raycast"
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local raycastParams = RaycastParams.new()
|
||||
raycastParams.CollisionGroup = "StudioSelectable" -- To follow the convention
|
||||
raycastParams.BruteForceAllSlow = true -- So that parts with CanQuery of "false" can be selected
|
||||
|
||||
local mouseLocation = UserInputService:GetMouseLocation()
|
||||
local mouseRay = Workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)
|
||||
local filteredSelectionHit = Workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 10000, raycastParams)
|
||||
```
|
||||
|
||||
### Part-to-part filtering
|
||||
|
||||
To prevent collisions between two specific parts without
|
||||
setting up [collision groups](#collision-groups), such as between a vehicle's wheel and its chassis, consider the
|
||||
[No Collision](../physics/mechanical-constraints.md#nocollisionconstraint)
|
||||
constraint. Advantages include:
|
||||
|
||||
- Collision groups and/or configuration scripts are not required, so you can easily create and share models with customized collision filtering.
|
||||
- Connected parts will not collide with each other, but they can still
|
||||
collide with other objects.
|
||||
|
||||
### Disable character collisions
|
||||
|
||||
Roblox player characters collide with each other by default. This can lead to
|
||||
interesting but unintended gameplay, such as characters jumping on top of each other to reach specific areas. If this behavior is undesirable, you can prevent it through the following `Class.Script` in `Class.ServerScriptService`.
|
||||
|
||||
```lua title="Script - Disable Character Collisions"
|
||||
local PhysicsService = game:GetService("PhysicsService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
PhysicsService:RegisterCollisionGroup("Characters")
|
||||
PhysicsService:CollisionGroupSetCollidable("Characters", "Characters", false)
|
||||
|
||||
local function onDescendantAdded(descendant)
|
||||
-- Set collision group for any part descendant
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.CollisionGroup = "Characters"
|
||||
end
|
||||
end
|
||||
|
||||
local function onCharacterAdded(character)
|
||||
-- Process existing and new descendants for physics setup
|
||||
for _, descendant in character:GetDescendants() do
|
||||
onDescendantAdded(descendant)
|
||||
end
|
||||
character.DescendantAdded:Connect(onDescendantAdded)
|
||||
end
|
||||
|
||||
Players.PlayerAdded:Connect(function(player)
|
||||
-- Detect when the player's character is added
|
||||
player.CharacterAdded:Connect(onCharacterAdded)
|
||||
end)
|
||||
```
|
||||
|
||||
## Model collisions
|
||||
|
||||
`Class.Model` objects are containers for parts rather than inheriting from `Class.BasePart`, so they can't directly connect to `Class.BasePart.Touched` or `Class.BasePart.TouchEnded` events. To determine whether a model triggers a collision events, you need to loop through its children and connect the custom `onTouched()` and `onTouchEnded()` functions to each child `Class.BasePart`.
|
||||
|
||||
<Alert severity='info'>
|
||||
For joined parts by [solid modeling](../parts/solid-modeling.md) instead of `Class.Model` objects, see [Mesh and Solid Modeling Collisions](#mesh-and-solid-model-collisions).
|
||||
</Alert>
|
||||
|
||||
The following code sample connects all `Class.BasePart|BaseParts` of a multi‑part model to collision events and tracks the total number of collisions with other parts.
|
||||
|
||||
```lua title="Model Collision"
|
||||
local model = script.Parent
|
||||
|
||||
local numTouchingParts = 0
|
||||
|
||||
local function onTouched(otherPart)
|
||||
-- Ignore instances of the model intersecting with itself
|
||||
if otherPart:IsDescendantOf(model) then return end
|
||||
-- Increase count of model parts touching
|
||||
numTouchingParts += 1
|
||||
|
||||
print(model.Name, "intersected with", otherPart.Name, "| Model parts touching:", numTouchingParts)
|
||||
end
|
||||
|
||||
local function onTouchEnded(otherPart)
|
||||
-- Ignore instances of the model un-intersecting with itself
|
||||
if otherPart:IsDescendantOf(model) then return end
|
||||
-- Decrease count of model parts touching
|
||||
numTouchingParts -= 1
|
||||
|
||||
print(model.Name, "un-intersected from", otherPart.Name, "| Model parts touching:", numTouchingParts)
|
||||
end
|
||||
|
||||
for _, child in model:GetChildren() do
|
||||
if child:IsA("BasePart") then
|
||||
child.Touched:Connect(onTouched)
|
||||
child.TouchEnded:Connect(onTouchEnded)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Mesh and solid model collisions
|
||||
|
||||
`Class.MeshPart` and `Class.PartOperation` (parts joined by [solid modeling](../parts/solid-modeling.md)) are subclasses of `Class.BasePart`, so meshes and solid modeled parts inherit the same [collision events](#collision-events) and [collision filtering](#collision-filtering) options as regular parts. However, since meshes and solid modeled parts usually have more complex geometries, they have a distinctive `Class.TriangleMeshPart.CollisionFidelity|CollisionFidelity` property which determines how precisely the physical bounds align with the visual representation for collision handling.
|
||||
|
||||
The `Class.TriangleMeshPart.CollisionFidelity|CollisionFidelity` property has the following options, in order of fidelity and performance impact from lowest to highest:
|
||||
|
||||
- **Box** — Creates a bounding collision box, ideal for small or non‑interactive objects.
|
||||
- **Hull** — Generates a convex hull, suitable for objects with less pronounced indentations or cavities.
|
||||
- **Default** — Produces an approximate collision shape that supports concavity, suitable for complex objects with semi-detailed interaction needs.
|
||||
- **PreciseConvexDecomposition** — Offers the most precise fidelity but still not a 1:1 representation of the visual. This option has the most expensive performance cost and takes longer for the engine to compute.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Original Mesh">
|
||||
<img src="../assets/physics/collisions/Collision-Fidelity-MeshPart.jpg" width="600" height="500" alt="Original mesh of castle tower" />
|
||||
</TabItem>
|
||||
<TabItem label="Default">
|
||||
<img src="../assets/physics/collisions/Collision-Fidelity-Default.jpg" width="600" height="500" alt="Collision fidelity of Default shown for mesh" />
|
||||
</TabItem>
|
||||
<TabItem label="Box">
|
||||
<img src="../assets/physics/collisions/Collision-Fidelity-Box.jpg" width="600" height="500" alt="Collision fidelity of Box shown for mesh"/>
|
||||
</TabItem>
|
||||
<TabItem label="Hull">
|
||||
<img src="../assets/physics/collisions/Collision-Fidelity-Hull.jpg" width="600" height="500" alt="Collision fidelity of Hull shown for mesh" />
|
||||
</TabItem>
|
||||
<TabItem label="Precise">
|
||||
<img src="../assets/physics/collisions/Collision-Fidelity-Precise.jpg" width="600" height="500" alt="Collision fidelity of PreciseConvexDecomposition shown for mesh" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Alert severity="success">
|
||||
To view collision fidelity in Studio, toggle on **Collision fidelity** from the [Visualization Options](../studio/ui-overview.md#visualization-options) widget in the upper‑right corner of the 3D viewport.
|
||||
</Alert>
|
||||
|
||||
For more information on the performance impact of collision fidelity options and how to mitigate them, see [Performance optimization](../performance-optimization/improve.md#physics-computation).
|
||||
116
.cursor/rules/roblox/en-us/workspace/index.md
Normal file
116
.cursor/rules/roblox/en-us/workspace/index.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: 3D workspace
|
||||
description: Describes the workspace container service, which holds all objects that exist in the 3D world.
|
||||
---
|
||||
|
||||
`Class.Workspace` is a container service that holds objects that you want the
|
||||
Roblox Engine to render in the 3D world. You typically will add these objects to
|
||||
the workspace:
|
||||
|
||||
- `Class.BasePart` objects, which includes both `Class.Part` and `Class.MeshPart` objects.
|
||||
- `Class.Attachment` objects, which you can attach to special effect generators like a `Class.ParticleEmitter`, UI objects like a `Class.BillboardGui`, physical `Class.Constraint|Constraints`, and more.
|
||||
- `Class.Model` objects that organize geometric groupings.
|
||||
- `Class.Script` objects that are parented by other objects in the workspace.
|
||||
Scripts aren't rendered but can affect another object's rendering.
|
||||
|
||||
## Parts
|
||||
|
||||
`Class.Part` objects represent the primitive building blocks in Roblox. By default, all parts have their physics simulated and are rendered if they appear in the 3D workspace. Parts can take the shape of blocks, spheres, cylinders, wedges, or corner wedges. In addition, `Class.TrussPart` acts as a truss beam that characters can climb like a ladder.
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../assets/modeling/parts/Basic-Part-Block.png" alt="A single Block part." />
|
||||
<figcaption><center>Block</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../assets/modeling/parts/Basic-Part-Sphere.png" alt="A single Sphere part." />
|
||||
<figcaption><center>Sphere</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../assets/modeling/parts/Basic-Part-Cylinder.png" alt="A single Cylinder part." />
|
||||
<figcaption><center>Cylinder</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../assets/modeling/parts/Basic-Part-Wedge.png" alt="A single Wedge part." />
|
||||
<figcaption><center>Wedge</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item XSmall={4} XLarge={2}>
|
||||
<figure>
|
||||
<img src="../assets/modeling/parts/Basic-Part-Corner-Wedge.png" alt="A single Corner Wedge part." />
|
||||
<figcaption><center>Corner Wedge</center></figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
You can also apply [solid modeling](../parts/solid-modeling.md) operations to parts, such as union or negate, to combine them into something more complex like bowls or hollow pipes.
|
||||
|
||||
## Meshes
|
||||
|
||||
A `Class.MeshPart` is an object that represents a mesh (a collection of
|
||||
vertices, edges, and faces that make up a 3D object). You typically create
|
||||
meshes using third-party software such as [Blender](https://www.blender.org) or [Maya](https://www.autodesk.com/products/maya/overview),
|
||||
then import them as a `Class.MeshPart` using Studio.
|
||||
|
||||
Meshes can include far more detail than any solid modeling you can do in
|
||||
Studio. They can also have internal rigs and textures, allowing you to
|
||||
create lifelike objects that you can pose and animate.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/meshes/Mesh-Textured.jpg" alt="A high-quality treasure chest mesh with a texture." />
|
||||
<figcaption>Mesh with texture</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/meshes/Mesh-SurfaceAppearance.jpg" alt="A realistic looking leafy bush with shadows and depth."/>
|
||||
<figcaption>Mesh with SurfaceAppearance</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Terrain
|
||||
|
||||
The `Class.Terrain` object allows you to generate and sculpt detailed and realistic terrain environments, such as mountains, bodies of water, grass-covered hills, or a flat desert. Using the [Terrain Editor](../studio/terrain-editor.md), you can easily generate and alter large regions of terrain.
|
||||
|
||||
<img src="../assets/modeling/terrain/Showcase.jpg" width="100%" alt="A viewport view of desert terrain with mountains in the distance." />
|
||||
|
||||
## Models
|
||||
|
||||
A `Class.Model` is a container object for **geometric groupings**, such as
|
||||
`Class.BasePart`, `Class.Motor6D` objects, and other models. Models can be simple
|
||||
groupings or you can set a primary part within the model, so that it functions as
|
||||
an assembly, which the physics engine treats as a single rigid body. Models can
|
||||
also contain scripts that act on the individual objects of the model.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/modeling/model-objects/Model-Example-3D.jpg" alt="A humanoid model of a creepy girl with four red eyes standing in an A pose. She wears a maroon dress with webs and bright red stockings." />
|
||||
<figcaption>A model named Octavia</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/model-objects/Model-Example-Hierarchy.png" width="320" alt="A close up view of the model's children in the Explorer window that comprise the model." />
|
||||
<figcaption>The groupings that comprise the model</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Workspace access in scripts
|
||||
|
||||
Within a script, you can access a place's `Class.Workspace` in three different ways, all of which are valid.
|
||||
|
||||
- `game:GetService("Workspace")`
|
||||
- `workspace`
|
||||
- `game.Workspace`
|
||||
|
||||
From there, you can carry out a large set of use cases to script logic for your
|
||||
experiences and create dynamic worlds and interactions. For example:
|
||||
|
||||
- Obtain a reference to any object in the workspace to change its properties during runtime.
|
||||
- Obtain a reference to a user's `Class.Camera` object to [manipulate their view](../workspace/camera.md) of the workspace.
|
||||
- Listen for events on objects in the workspace to carry out
|
||||
logic at specific times, such as when a user's playable character [touches an object](../workspace/collisions.md).
|
||||
158
.cursor/rules/roblox/en-us/workspace/raycasting.md
Normal file
158
.cursor/rules/roblox/en-us/workspace/raycasting.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
---
|
||||
title: Raycasting
|
||||
description: Raycasting allows you to detect collisions and determine the position of objects.
|
||||
---
|
||||
|
||||
At its most basic level, **raycasting** is the act of sending out an invisible ray from a `Datatype.Vector3` point in a specific direction with a defined length. Once cast, you can detect if the ray hits a `Class.BasePart` or `Class.Terrain` cell.
|
||||
|
||||
<figure>
|
||||
<video controls src="../assets/workspace/raycasting/Laser-Maze.mp4" width="100%"></video>
|
||||
<figcaption>Lasers are fired by floating orbs, and raycasting determines whether a laser hits a platform. Platforms touched by the lasers are temporarily destroyed.</figcaption>
|
||||
</figure>
|
||||
|
||||
You can cast a ray with the `Class.WorldRoot:Raycast()` method (`Class.Workspace:Raycast()`) from a `Datatype.Vector3` origin in a `Datatype.Vector3` direction.
|
||||
|
||||
```lua title="Basic Raycast" highlight="4"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local rayOrigin = Vector3.new(0, 0, 0)
|
||||
local rayDirection = Vector3.new(0, -100, 0)
|
||||
|
||||
local raycastResult = Workspace:Raycast(rayOrigin, rayDirection)
|
||||
```
|
||||
|
||||
<Alert severity="warning">
|
||||
When casting a ray, the direction parameter should encompass the desired length of the ray. For instance, if the magnitude of the direction is 10, the resulting ray will also be of length 10. The maximum length is 15,000 studs.
|
||||
</Alert>
|
||||
|
||||
When applicable, you can calculate an unknown directional vector (`rayDirection`) using a known **origin** and **destination**. This is useful when casting a ray between two points that can change, such as from one player character to another.
|
||||
|
||||
1. The origin plus a directional vector indicate the ray's destination:
|
||||
|
||||
$\text{rayOrigin} + \text{rayDirection} = \text{rayDestination}$
|
||||
|
||||
2. Subtract $\text{rayOrigin}$ from both sides of the equation:
|
||||
|
||||
$\text{rayOrigin} + \text{rayDirection} - \text{rayOrigin} = \text{rayDestination} - \text{rayOrigin}$
|
||||
|
||||
3. The ray's direction equals the destination minus the origin:
|
||||
|
||||
$\text{rayDirection} = \text{rayDestination} - \text{rayOrigin}$
|
||||
|
||||
```lua highlight="4"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local rayOrigin = Workspace.TestOrigin.Position
|
||||
local rayDestination = Workspace.TestDestination.Position
|
||||
|
||||
local rayDirection = rayDestination - rayOrigin
|
||||
|
||||
local raycastResult = Workspace:Raycast(rayOrigin, rayDirection)
|
||||
```
|
||||
|
||||
## Filter options
|
||||
|
||||
`Class.WorldRoot:Raycast()` accepts an optional `Datatype.RaycastParams` object which tells the raycast to selectively include or exclude certain `Class.BasePart|BaseParts`, ignore the **Water** material for `Class.Terrain`, or use a [collision group](../workspace/collisions.md#collision-filtering).
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`FilterDescendantsInstances`</td>
|
||||
<td>`Array` of objects whose descendants are used in filtering raycasting candidates.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`FilterType`</td>
|
||||
<td>`Enum.RaycastFilterType` enum which determines how the `FilterDescendantsInstances` array is used in the raycast operation.<ul><li>`Enum.RaycastFilterType|Exclude` — Every `Class.BasePart` is considered **except** those that are descendants of objects in the array.</li><li>`Enum.RaycastFilterType|Include` — Only `Class.BasePart|BaseParts` that are descendants of objects in the array are considered.</li></ul></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`IgnoreWater`</td>
|
||||
<td>Boolean which determines whether the **Water** material is considered when raycasting against `Class.Terrain`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`CollisionGroup`</td>
|
||||
<td>String name of the [collision group](../workspace/collisions.md#collision-filtering) used for the raycasting operation.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```lua title="Raycast Filtering" highlight="4-7,9"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local rayOrigin = Vector3.zero
|
||||
local rayDirection = Vector3.new(0, -100, 0)
|
||||
|
||||
local raycastParams = RaycastParams.new()
|
||||
raycastParams.FilterDescendantsInstances = {script.Parent}
|
||||
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
|
||||
raycastParams.IgnoreWater = true
|
||||
|
||||
local raycastResult = Workspace:Raycast(rayOrigin, rayDirection, raycastParams)
|
||||
```
|
||||
|
||||
## Hit detection
|
||||
|
||||
If the raycasting operation hits an eligible `Class.BasePart` or `Class.Terrain` cell, a `Datatype.RaycastResult` object is returned containing the results. To test for a hit, confirm that the result is not `nil` and utilize the following properties as needed.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Instance`</td>
|
||||
<td>The `Class.BasePart` or `Class.Terrain` cell that the ray intersected.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Position`</td>
|
||||
<td>`Datatype.Vector3` position of the intersection between the ray and the `Class.Instance`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Distance`</td>
|
||||
<td>Distance between the ray origin and intersection point.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Material`</td>
|
||||
<td>The `Enum.Material` of the `Class.BasePart` or `Class.Terrain` at the intersection point.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Normal`</td>
|
||||
<td>`Datatype.Vector3` of the normal vector of the intersected face.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="warning">
|
||||
When used in a place with instance [streaming](../workspace/streaming.md) enabled, a client may not have streamed in distant `Class.BasePart|BaseParts`, so a raycast may not detect a hit. Additionally, low-detail "imposter" `Class.Terrain` and `Class.Model|Models` generated by the streaming system are purely visual and are not eligible raycast targets.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
You can exempt any `Class.BasePart` from hit detection by setting its `Class.BasePart.CanQuery` property to `false`.
|
||||
</Alert>
|
||||
|
||||
```lua title="Raycast Hit Detection" highlight="7-11"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local rayOrigin = Vector3.zero
|
||||
local rayDirection = Vector3.new(0, -100, 0)
|
||||
|
||||
local raycastResult = Workspace:Raycast(rayOrigin, rayDirection)
|
||||
|
||||
if raycastResult then
|
||||
print("Instance:", raycastResult.Instance)
|
||||
print("Position:", raycastResult.Position)
|
||||
print("Distance:", raycastResult.Distance)
|
||||
print("Material:", raycastResult.Material)
|
||||
print("Normal:", raycastResult.Normal)
|
||||
else
|
||||
warn("No raycast result!")
|
||||
end
|
||||
```
|
||||
509
.cursor/rules/roblox/en-us/workspace/streaming.md
Normal file
509
.cursor/rules/roblox/en-us/workspace/streaming.md
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
---
|
||||
title: Instance streaming
|
||||
description: Instance streaming allows the Roblox Engine to dynamically load and unload 3D content in regions of the world.
|
||||
---
|
||||
|
||||
import BetaAlert from '../includes/beta-features/beta-alert.md'
|
||||
|
||||
In-experience **instance streaming** allows the Roblox Engine to dynamically load and unload 3D content and related instances in regions of the world. This can improve the overall player experience in several ways, for example:
|
||||
|
||||
- **Faster join times** — Players can start playing in one part of the world while more of the world loads in the background.
|
||||
- **Memory efficiency** — Experiences can be played on devices with less memory since content is dynamically streamed in and out. More immersive and detailed worlds can be played on a wider range of devices.
|
||||
- **Improved performance** — Better frame rates and performance, as the server can spend less time and bandwidth synchronizing changes between the world and players in it. Clients spend less time updating instances that aren't currently relevant to the player.
|
||||
- **Level of detail** — Distant models and terrain remain visible even when they're not streamed to clients, keeping the experience optimized without entirely sacrificing background visuals.
|
||||
|
||||
## Enable streaming
|
||||
|
||||
Instance streaming is enabled through the **StreamingEnabled** property of the **Workspace** object in Studio. This property cannot be set in a script. Streaming is enabled by default for new places created in Studio.
|
||||
|
||||
<img src="../assets/studio/properties/Workspace-StreamingEnabled.png" width="320" alt="The Properties window with the StreamingEnabled property enabled." />
|
||||
|
||||
Once enabled, it's recommended that you adhere to the following practices:
|
||||
|
||||
- Because clients will not typically have the entire `Class.Workspace` available locally, use the appropriate tool/API to ensure that instances exist before attempting to access them in a `Class.LocalScript`. For example, utilize [per‑model streaming controls](#per-model-streaming-controls), [detect instance streaming](#detect-instance-streaming), or use `Class.Instance:WaitForChild()|WaitForChild()` on objects that may not exist.
|
||||
- Minimize placement of 3D content outside of `Class.Workspace`. Content in containers such as `Class.ReplicatedStorage` or `Class.ReplicatedFirst` is ineligible for streaming and may negatively impact join time and memory usage.
|
||||
- If you move a player's character by setting its `Datatype.CFrame`, do so from a server-side `Class.Script` and use [streaming requests](#request-area-streaming) to more quickly load data around the character's new location.
|
||||
- Manually set [replication foci](#replication-focus) only in unique situations such as experiences that don't use a `Class.Player.Character` or where streaming should occur in multiple areas of the experience. In these cases, make sure the foci are near objects that the player controls or those that should continue simulating physically on the client, and try to minimize the overall number of foci used.
|
||||
|
||||
## Technical behavior
|
||||
|
||||
### Stream in
|
||||
|
||||
By default, when a player joins an experience with instance streaming enabled, instances in the `Class.Workspace` are replicated to the client, **excluding** the following:
|
||||
|
||||
- `Class.Part|Parts` or `Class.MeshPart|MeshParts`
|
||||
- [Atomic](#atomic), [Persistent](#persistent), or [PersistentPerPlayer](#persistentperplayer) models
|
||||
- Descendants of the above instances
|
||||
- Non-replicating instances
|
||||
|
||||
Then, during gameplay, the server may stream necessary instances to the client, as they are needed.
|
||||
|
||||
<img src="../assets/optimization/streaming/Technical-Behavior.svg" width="800" height="464" alt="Diagram showing when various instances and their descendants in the Workspace stream in." />
|
||||
|
||||
<figcaption><sup>1</sup> Terrain is treated uniquely, in that the instance replicates to the client when the experience loads, but terrain regions only stream in when needed</figcaption><br />
|
||||
|
||||
<h4>Model behavior</h4>
|
||||
|
||||
Models set to non-default behavior like [Atomic](#atomic) stream in under special rules as outlined in [per‑model streaming controls](#per-model-streaming-controls). However, default (nonatomic) models are sent differently based on whether [ModelStreamingBehavior](#modelstreamingbehavior) is set to **Default** (**Legacy**) or **Improved**.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Default / Legacy">
|
||||
|
||||
<img src="../assets/studio/properties/Workspace-ModelStreamingBehavior-Default.png" width="320" height="121" alt="The Properties window with the ModelStreamingBehavior property set to Default." />
|
||||
|
||||
When [ModelStreamingBehavior](#modelstreamingbehavior) is set to **Default**/**Legacy**, the `Class.Model` container and its non‑spatial descendants such as `Class.Script|Scripts` replicate to the client when the player joins. Then, when eligible, the model's `Class.BasePart` descendants stream in.
|
||||
|
||||
<img src="../assets/optimization/streaming/Model-Stream-In-Default.svg" width="800" height="176" alt="Diagram showing default model stream in behavior." />
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Improved">
|
||||
|
||||
<img src="../assets/studio/properties/Workspace-ModelStreamingBehavior-Improved.png" width="320" height="121" alt="The Properties window with the ModelStreamingBehavior property set to Improved." />
|
||||
|
||||
When [ModelStreamingBehavior](#modelstreamingbehavior) is set to **Improved**, model streaming behavior varies by whether the model is **spatial** (contains `Class.BasePart` descendants) or **non‑spatial** (contains no `Class.BasePart` descendants).
|
||||
|
||||
- Instead of on player join, a **spatial** model (one containing `Class.BasePart` descendants) is sent only when one of its `Class.BasePart` descendants is eligible to stream in. At that point, the model and part are replicated, along with the model's non‑spatial descendants. Then, when eligible, the model's other spatial descendants stream in.
|
||||
|
||||
<img src="../assets/optimization/streaming/Model-Stream-In-Improved-Spatial.svg" width="800" height="176" alt="A diagram showing improved spatial model stream in behavior." />
|
||||
|
||||
- A key consideration is when a spatial model and all of its `Class.BasePart|BaseParts` belong to a single [network ownership](../physics/network-ownership.md) unit, such as an avatar or NPC character model. In such cases, the entire model will stream in atomically.
|
||||
|
||||
<img src="../assets/optimization/streaming/Model-Stream-In-Improved-Character.svg" width="800" height="400" alt="Diagram showing improved character model stream in behavior." />
|
||||
|
||||
- For a **non‑spatial** model (one without `Class.BasePart` descendants), the model container and its descendants are replicated to the client soon after the player joins, and all are exempt from streaming out. Assuming the model exists in `Class.Workspace` when the player joins, this occurs before the `Class.Workspace.PersistentLoaded` event fires.
|
||||
|
||||
<img src="../assets/optimization/streaming/Model-Stream-In-Improved-Non-Spatial.svg" width="800" height="176" alt="Diagram showing improved non-spatial model stream in behavior." />
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Stream out
|
||||
|
||||
During gameplay, a client may stream out (remove from the player's `Class.Workspace`) regions and the `Class.BasePart|BaseParts` contained within them, based on the behavior set by [StreamOutBehavior](#streamoutbehavior). The process begins with regions furthest away from the [replication foci](#replication-focus) and moves in closer as needed. Regions inside the [StreamingMinRadius](#streamingminradius) range never stream out.
|
||||
|
||||
When an instance streams out, it is parented to `nil` so that any existing Luau state will reconnect if the instance streams back in. As a result, removal signals such as `Class.Instance.ChildRemoved|ChildRemoved` or `Class.Instance.DescendantRemoving|DescendantRemoving` fire on its **parent** or **ancestor**, but the instance itself is not destroyed in the same sense as an `Class.Instance:Destroy()` call.
|
||||
|
||||
To further anticipate stream out, examine these scenarios:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Scenario</th>
|
||||
<th>Example</th>
|
||||
<th>Streaming behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>A part is **created** locally through `Class.Instance.new()` in a `Class.LocalScript`.</td>
|
||||
<td>In a "capture the flag" game, you create and attach blue helmet parts to all players on the blue team through a `Class.LocalScript`.</td>
|
||||
<td>The part is not replicated to the server, and it is exempt from streaming out **unless** you make it a descendant of a part that exists on the server, such as a part within a player's character model.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A part is **cloned** locally from `Class.ReplicatedStorage` through `Class.Instance:Clone()` in a `Class.LocalScript`.</td>
|
||||
<td>A wizard character casts a spell by activating a `Class.Tool`, upon which an object including several [special effects](../environment/index.md#special-effects) is cloned from `Class.ReplicatedStorage` and parented to the workspace at the wizard's position.</td>
|
||||
<td>The part is not replicated to the server, and it is exempt from streaming out **unless** you make it a descendant of a part that exists on the server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A part is **reparented** from `Class.ReplicatedStorage` to the workspace via a `Class.LocalScript`.</td>
|
||||
<td>A "wizard's hat" is stored in `Class.ReplicatedStorage`. When a player chooses to play on the wizard's team, the hat is moved into their character model via a `Class.LocalScript`.</td>
|
||||
<td>The part remains eligible for streaming out since it came from the server and was replicated to `Class.ReplicatedStorage`. Avoid this pattern as it causes a desync between the client and server, and the part may stream out; instead, **clone** the part.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>Model behavior</h4>
|
||||
|
||||
If you set [ModelStreamingBehavior](#modelstreamingbehavior) to **Improved**, the engine may stream out [Default](#default--nonatomic) ([Nonatomic](#default--nonatomic)) models when they're eligible to stream out, potentially freeing up memory on the client and reducing the instances which need property updates.
|
||||
|
||||
<img src="../assets/studio/properties/Workspace-ModelStreamingBehavior-Improved.png" width="320" alt="The Properties window with the ModelStreamingBehavior property set to Improved." />
|
||||
|
||||
Under **Improved** model streaming behavior, streaming out of [Default](#default--nonatomic) ([Nonatomic](#default--nonatomic)) models is based on whether the model is **spatial** (contains `Class.BasePart` descendants) or **non‑spatial** (contains no `Class.BasePart` descendants).
|
||||
|
||||
- A spatial model only streams out completely when its last remaining `Class.BasePart` descendant streams out, since some of the model's spatial parts may be near to the player/replication focus and some far away.
|
||||
- A non‑spatial model only streams out when an ancestor streams out, equivalent to legacy streaming out behavior.
|
||||
|
||||
### Assemblies and mechanisms
|
||||
|
||||
When at least one part of an [assembly](../physics/assemblies.md) is eligible for streaming in, all of the assembly's parts also stream in. However, an assembly will not stream **out** until **all** of its parts are eligible for streaming out. During streaming, all of the `Class.Constraint|Constraints` and `Class.Attachment|Attachments` descending from `Class.BasePart|BaseParts` and atomic or persistent `Class.Model|Models` also stream, helping to ensure consistent physics updates on clients.
|
||||
|
||||
Note that assemblies with **anchored** parts are treated slightly differently than assemblies with only unanchored parts:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Assembly composition</th>
|
||||
<th>Streaming behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Unanchored parts only</td>
|
||||
<td>Entire assembly is sent as an atomic unit.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Anchored [root part](../physics/assemblies.md#assembly-root-part)</td>
|
||||
<td>Only the parts, attachments, and constraints needed to link the streamed parts to the root part are streamed in together.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="warning">
|
||||
Avoid creating moving assemblies with unnecessarily large numbers of instances, as all of the instances streaming in unison may cause network/CPU spikes.
|
||||
</Alert>
|
||||
|
||||
### Timing delay
|
||||
|
||||
There may be a slight delay of ~10 milliseconds between when a part is created on the server and when it gets replicated to clients. In each of the following scenarios, you may need to use `Class.Instance:WaitForChild()|WaitForChild()` and other techniques rather than assuming that events and property updates always occur at the same time as part streaming.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Scenario</th>
|
||||
<th>Example</th>
|
||||
<th>Streaming behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>A `Class.LocalScript` makes a `Class.RemoteFunction` call to the server to create a part.</td>
|
||||
<td>A player activates a `Class.Tool` locally to spawn a part on the server that all players can see and interact with.</td>
|
||||
<td>When the remote function returns to the client, the part may not yet exist, even though the part is close to the client focus and within a streamed area.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A part is added to a character model on the server via a `Class.Script` and a `Class.RemoteEvent` is fired to a client.</td>
|
||||
<td>When a player joins the police team, a "police badge" part stored in `Class.ServerStorage` is cloned and attached to the player's character model. A `Class.RemoteEvent` is fired and received by that player's client in order to update a local UI element.</td>
|
||||
<td>Although the client receives the event signal, there is no guarantee that the part has already streamed to that client.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>A part collides with an invisible region on the server and triggers a `Class.RemoteEvent` on the client.</td>
|
||||
<td>A player kicks a soccer ball into a goal, triggering a "goal scored" event. </td>
|
||||
<td>Other players that are close to the goal may see the "goal scored" event before the ball has been streamed to them.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Streaming properties
|
||||
|
||||
The following properties control how instance streaming applies to your experience. All of these properties are **non-scriptable** and must be set on the **Workspace** object in Studio.
|
||||
|
||||
<img src="../assets/studio/properties/Workspace-Streaming-Properties.png" width="320" alt="The Properties window with the ModelStreamingBehavior, StreamingIntegrityMode, StreamingMidRadius, StreamingTargetRadius, and StreamOutBehavior property highlighted." />
|
||||
|
||||
### ModelStreamingBehavior
|
||||
|
||||
Controls whether [Default](#default--nonatomic) ([Nonatomic](#default--nonatomic)) models are replicated when a player joins, or are only sent when needed. If this property is set to **Improved**, models in `Class.Workspace` will only be sent to clients when needed, potentially speeding up join times. See [Technical Behavior](#technical-behavior) for more details.
|
||||
|
||||
### StreamingIntegrityMode
|
||||
|
||||
Your experience may behave in unintended ways if a player moves into a region of the world that hasn't been streamed to them. The **streaming integrity** feature offers a way to avoid those potentially problematic situations. Please see the `Enum.StreamingIntegrityMode` documentation for more details.
|
||||
|
||||
### StreamingMinRadius
|
||||
|
||||
The **StreamingMinRadius** property indicates the radius around the [replication foci](#replication-focus) in which instances stream in at the highest priority. Care should be taken when increasing the default, as doing so will require more memory and more server bandwidth at the expense of other components.
|
||||
|
||||
### StreamingTargetRadius
|
||||
|
||||
The **StreamingTargetRadius** property controls the maximum distance away from the [replication foci](#replication-focus) in which instances stream in. Note that the engine is allowed to retain previously loaded instances beyond the target radius, memory permitting.
|
||||
|
||||
A smaller **StreamingTargetRadius** reduces server workload, as the server will not stream in additional instances beyond the set value. However, the target radius is also the maximum distance players will be able to see the full detail of your experience, so you should pick a value that creates a nice balance between these.
|
||||
|
||||
<Alert severity="info">
|
||||
**StreamingTargetRadius** should be larger than **StreamingMinRadius**. 3D content between the target radius and the minimum radius acts as a buffer in case the client temporarily stops receiving new content from the server. When the minimum radius and the target radius are equal, there is no buffer, which can lead to an increase in network pauses or an otherwise suboptimal user experience.
|
||||
</Alert>
|
||||
|
||||
### StreamOutBehavior
|
||||
|
||||
The **StreamOutBehavior** property sets the [streaming out](#stream-out) behavior according to one of the following values:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Streaming behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Default**</td>
|
||||
<td>Default behavior, currently the same as **LowMemory**.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**LowMemory**</td>
|
||||
<td>The client only streams out parts in a low memory situation and may remove 3D content until only the minimum radius is present.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Opportunistic**</td>
|
||||
<td>Regions beyond [StreamingTargetRadius](#streamingtargetradius) can be removed on the client even when there is no memory pressure. In this mode, the client never removes instances that are closer than the target radius, except in low memory situations.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Replication focus
|
||||
|
||||
By default, streaming occurs around the local player's character's `Class.Model.PrimaryPart|PrimaryPart`, although you can specify a different replication focus point through `Class.Player.ReplicationFocus`.
|
||||
|
||||
You can also add and remove additional replication foci through `Class.Player:AddReplicationFocus()` and `Class.Player:RemoveReplicationFocus()` to dynamically enable streaming in multiple areas of the experience.
|
||||
|
||||
<Alert severity="warning">
|
||||
Use caution when adding additional replication foci as each additional focus increases the server's workload for streaming and updating regions. For example, a single player with nine dynamically moving foci could generate server networking and streaming processing comparable to ten players moving around the experience.
|
||||
|
||||
On the client, too many foci for a player can limit the engine's ability to adjust to memory limitations and make it more likely for clients to be killed by the OS for using too much memory.
|
||||
</Alert>
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Physics Simulation">
|
||||
Client-side physics simulation only occurs in streamed areas, even for locally created instances and for [persistent](#persistent) instances. If you have instances that you'd like to keep simulating even when they're far away from the character, create an additional replication focus near those instances.
|
||||
</TabItem>
|
||||
<TabItem label="Movement Between Zones">
|
||||
In many experiences, it's common for players to move back and forth between the same areas frequently, for example between their "home base" and a "trading hub." In such cases, you can create a replication focus point in each area to ensure those areas are readily present on client devices.
|
||||
</TabItem>
|
||||
<TabItem label="Distant Viewpoints">
|
||||
Multiple replication points are useful when players can view specific, important regions through a scope, such as enemy bases scattered across a barren landscape. In such cases, you can create a replication focus point in each base to ensure players see details and simulated physics from afar.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Per-model streaming controls
|
||||
|
||||
Globally, the [ModelStreamingBehavior](#modelstreamingbehavior) property lets you control how models are streamed in on join. Additionally, to avoid issues with streaming on a per-model basis and minimize use of `Class.Instance:WaitForChild()|WaitForChild()`, you can customize how `Class.Model|Models` and their descendants stream through their `Class.Model.ModelStreamingMode|ModelStreamingMode` property.
|
||||
|
||||
<img src="../assets/studio/properties/Model-ModelStreamingMode.png" width="320" alt="The Properties window with the ModelStreamingMode property set to Default. The property is also highlighted." />
|
||||
|
||||
### Default / Nonatomic
|
||||
|
||||
When a `Class.Model` is set to **Default** or **Nonatomic**, streaming behavior varies based on whether [ModelStreamingBehavior](#modelstreamingbehavior) is set to **Default** (**Legacy**) or **Improved**.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>[ModelStreamingBehavior](#modelstreamingbehavior)</th>
|
||||
<th>Technical behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Default** (**Legacy**)</td>
|
||||
<td>The model is replicated when a player joins. This potentially results in more instances sent during loading, more instances stored in memory, and additional complexity for scripts that want to access the model's descendants. For example, a separate `Class.LocalScript` will need to use `Class.Instance:WaitForChild()|WaitForChild()` on a descendant `Class.BasePart` inside the model.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Improved**</td>
|
||||
<td>The model is only sent when needed, potentially speeding up join times.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
See [technical behavior](#technical-behavior) for more details.
|
||||
|
||||
### Atomic
|
||||
|
||||
If a `Class.Model` is changed to **Atomic**, all of its descendants are streamed in together when a descendant `Class.BasePart` is eligible. As a result, a separate `Class.LocalScript` that needs to access instances in the model would need to use`Class.Instance:WaitForChild()|WaitForChild()` on the model itself, but not on a descendant `Class.MeshPart` or `Class.Part` since they are sent alongside the model.
|
||||
|
||||
An atomic model is only streamed out when all of its descendant parts are eligible for streaming out, at which point the entire model streams out together. If only some parts of an atomic model would typically be streamed out, the entire model and its descendants remain on the client.
|
||||
|
||||
<img src="../assets/optimization/streaming/ModelStreamingMode-Atomic.svg" width="800" height="336" alt="A diagram showing Atomic model streaming along with children." />
|
||||
|
||||
```lua title="LocalScript" highlight="2, 5-6"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
-- Atomic model does not exist at load time; use WaitForChild()
|
||||
local model = Workspace:WaitForChild("Model")
|
||||
|
||||
-- Descendant parts stream in with model and are immediately accessible
|
||||
local meshPart = model.MeshPart
|
||||
local part = model.Part
|
||||
```
|
||||
|
||||
### Persistent
|
||||
|
||||
**Persistent** models are not subject to normal streaming in or out. They are sent as a complete atomic unit soon after the player joins and before the `Class.Workspace.PersistentLoaded` event fires. Persistent models and their descendants are never streamed out, but to safely handle streaming in within a separate `Class.LocalScript`, you should use `Class.Instance:WaitForChild()|WaitForChild()` on the parent model, or wait for the `Class.Workspace.PersistentLoaded|PersistentLoaded` event to fire.
|
||||
|
||||
<img src="../assets/optimization/streaming/ModelStreamingMode-Persistent.svg" width="800" height="336" alt="A diagram showing Persistent model streaming along with children." />
|
||||
|
||||
```lua title="LocalScript" highlight="2, 5-6"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
-- Persistent model does not exist at load time; use WaitForChild()
|
||||
local model = Workspace:WaitForChild("Model")
|
||||
|
||||
-- Descendant parts stream in with model and are immediately accessible
|
||||
local meshPart = model.MeshPart
|
||||
local part = model.Part
|
||||
```
|
||||
|
||||
<Alert severity="warning">
|
||||
Persistent models are intended for very rare circumstances, such as when a small number of parts must always be present on clients for `Class.LocalScript` use. If possible, server-side `Class.Script|Scripts` should be used, or `Class.LocalScript|LocalScripts` should be tolerant of parts streaming in and out. Persistent models are **not** intended to circumvent streaming, and overuse may negatively impact performance.
|
||||
|
||||
Avoid creating catch-all persistent models that have a large number of sub-models. For example, if you're creating an experience with a large number of vehicles, do **not** create a single persistent model which contains every vehicle in the experience that you want to be persistent. Instead, set each individual vehicle model to be persistent, if necessary.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
Runtime performance impacts of persistent models after replication are mostly the same as regular models without streaming enabled. An exception is when the contents of the model change frequently or if parts in the model have changes to their physical connections. In these cases, the engine must perform extra updates to ensure those changes are reflected correctly on all clients, so it's best to avoid frequent changes to persistent models.
|
||||
</Alert>
|
||||
|
||||
### PersistentPerPlayer
|
||||
|
||||
Models set to **PersistentPerPlayer** behave the same as [Persistent](#persistent) for players that have been added using `Class.Model:AddPersistentPlayer()`. For other players, behavior is the same as [Atomic](#atomic). You can revert a model from player persistence via `Class.Model:RemovePersistentPlayer()`.
|
||||
|
||||
## Request area streaming
|
||||
|
||||
If you set the `Datatype.CFrame` of a player character to a region which isn't currently loaded, [streaming pause](#customize-the-pause-screen) occurs, if enabled through `Enum.StreamingIntegrityMode`. If you know the character will be moving to a specific area, you can call `Class.Player:RequestStreamAroundAsync()` to request that the server sends regions around that location to the client.
|
||||
|
||||
The following scripts show how to fire a client-to-server [remote event](../scripting/events/remote.md) to teleport a player within a place, yielding at the streaming request before moving the character to a new `Datatype.CFrame`.
|
||||
|
||||
```lua title="Script - Teleport Player Character" highlight="7, 10-14"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
|
||||
|
||||
local function teleportPlayer(player, teleportTarget)
|
||||
-- Request streaming around target location
|
||||
player:RequestStreamAroundAsync(teleportTarget)
|
||||
|
||||
-- Teleport character
|
||||
local character = player.Character
|
||||
if character and character.Parent then
|
||||
local currentPivot = character:GetPivot()
|
||||
character:PivotTo(currentPivot * CFrame.new(teleportTarget))
|
||||
end
|
||||
end
|
||||
|
||||
-- Call teleport function when the client fires the remote event
|
||||
teleportEvent.OnServerEvent:Connect(teleportPlayer)
|
||||
```
|
||||
|
||||
```lua title="LocalScript - Fire Remote Event"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local teleportEvent = ReplicatedStorage:WaitForChild("TeleportEvent")
|
||||
local teleportTarget = Vector3.new(50, 2, 120)
|
||||
|
||||
-- Fire the remote event
|
||||
teleportEvent:FireServer(teleportTarget)
|
||||
```
|
||||
|
||||
<Alert severity="error">
|
||||
Requesting streaming around an area is **not a guarantee** that the content will be present when the request completes, as streaming is affected by the client's network bandwidth, memory limitations, and other factors.
|
||||
</Alert>
|
||||
|
||||
## Customize the pause screen
|
||||
|
||||
If players can encounter streaming pauses in your experience, you might want to customize the pause screen. The `Class.Player.GameplayPaused` property indicates the player's current pause state. This property can be used with a `Class.Instance:GetPropertyChangedSignal()|GetPropertyChangedSignal()` connection to show or hide a custom GUI.
|
||||
|
||||
```lua title="LocalScript"
|
||||
local Players = game:GetService("Players")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local player = Players.LocalPlayer
|
||||
|
||||
-- Disable default pause modal
|
||||
GuiService:SetGameplayPausedNotificationEnabled(false)
|
||||
|
||||
local function onPauseStateChanged()
|
||||
if player.GameplayPaused then
|
||||
-- Show custom GUI
|
||||
else
|
||||
-- Hide custom GUI
|
||||
end
|
||||
end
|
||||
|
||||
player:GetPropertyChangedSignal("GameplayPaused"):Connect(onPauseStateChanged)
|
||||
```
|
||||
|
||||
## Detect instance streaming
|
||||
|
||||
In some cases, it's necessary to detect when an object streams in or out and react to that event. A useful pattern for streaming detection is as follows:
|
||||
|
||||
1. Using the [Tags](../studio/properties.md#instance-tags) section of an instance's properties, assign a logical `Class.CollectionService` tag to all of the affected objects.
|
||||
|
||||
2. From a single `Class.LocalScript`, detect when a tagged object streams in or out through `Class.CollectionService:GetInstanceAddedSignal()|GetInstanceAddedSignal()` and `Class.CollectionService:GetInstanceRemovedSignal()|GetInstanceRemovedSignal()`, then handle the object accordingly. For example, the following code adds tagged `Class.Light` objects into a "flicker" loop when they stream in and removes them when they stream out.
|
||||
|
||||
```lua title="LocalScript - CollectionService Streaming Detection" highlight="10-15"
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
|
||||
local tagName = "FlickerLightSource"
|
||||
local random = Random.new()
|
||||
local flickerSources = {}
|
||||
|
||||
-- Detect currently and new tagged parts streaming in or out
|
||||
for _, light in CollectionService:GetTagged(tagName) do
|
||||
flickerSources[light] = true
|
||||
end
|
||||
|
||||
CollectionService:GetInstanceAddedSignal(tagName):Connect(function(light)
|
||||
flickerSources[light] = true
|
||||
end)
|
||||
|
||||
CollectionService:GetInstanceRemovedSignal(tagName):Connect(function(light)
|
||||
flickerSources[light] = nil
|
||||
end)
|
||||
|
||||
-- Flicker loop
|
||||
while true do
|
||||
for light in flickerSources do
|
||||
light.Brightness = 8 + random:NextNumber(-0.4, 0.4)
|
||||
end
|
||||
|
||||
task.wait(0.05)
|
||||
end
|
||||
```
|
||||
|
||||
## Model level of detail
|
||||
|
||||
<Alert severity="success">
|
||||
SLIM meshes are currently in client beta for Windows and macOS.
|
||||
</Alert>
|
||||
|
||||
When streaming is enabled, `Class.Model|Models` outside of the currently streamed area are not visible by default. However, you can instruct the engine to render lightweight "SLIM" meshes or low resolution "imposter" meshes for models that are not present on clients. You control this behavior through each model's `Class.Model.LevelOfDetail|LevelOfDetail` property.
|
||||
|
||||
<img src="../assets/studio/properties/Model-LevelOfDetail.png" width="320" alt="LevelOfDetail property indicated for Model instance"/>
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/modeling/model-objects/LevelOfDetail-Original.jpg" alt="A globe model displays in its actual level of detail." />
|
||||
<figcaption>Original model</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/model-objects/LevelOfDetail-Slim.jpg" alt="The same globe model with a SLIM mesh, which is recognizable as the original globe." />
|
||||
<figcaption>Lightweight "SLIM" mesh (beta)</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/modeling/model-objects/LevelOfDetail-Imposter.jpg" alt="The same globe model as a low resolution imposter mesh with rough edges that obscure the globe's details." />
|
||||
<figcaption>Low resolution "imposter" mesh</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model setting</th>
|
||||
<th>Behavior outside of streaming radius</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**SLIM**</td>
|
||||
<td>
|
||||
Display a composite mesh of all child parts in the model when the original model is not present on the client. Roblox automatically generates many SLIM (Scalable Lightweight Interactive Model) meshes for the model and uses progressively less complex ones as distance from the camera increases.
|
||||
- Visual quality is superior to imposter meshes, with comparable performance.
|
||||
- Whether SLIM meshes display at all depends on the position of the **character**. The quality level of SLIM meshes depends on the position of the **camera**.
|
||||
- SLIM currently only supports models with static meshes, not skinned meshes, avatars, or animations.
|
||||
- The first time a model set to `Enum.ModelLevelOfDetail.SLIM|SLIM` is loaded, Roblox generates its optimized assets in the cloud. This one-time process can take a few moments for very complex models. If you have a large place and don't see some models immediately, wait a few minutes and try again.
|
||||
- SLIM relies on models to understand how to group geometry. For best results, use `Class.Model|Models` to group parts that are spatially and logically related (for example, all the parts of a car). For arbitrary organization in your project, use folders, which SLIM ignores.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**StreamingMesh**</td>
|
||||
<td>
|
||||
Display an imposter mesh when the model is not present on the client.
|
||||
- If a model and its descendant models are all set to `Enum.ModelLevelOfDetail.StreamingMesh|StreamingMesh`, only the top-level model is rendered as an imposter mesh, wrapping all geometries under it as well as its descendant models. For better performance, set descendant models to **Disabled**.
|
||||
- Imposter meshes look best at **1024 studs away from the camera** or further. If you reduce [StreamingTargetRadius](#streamingtargetradius) to smaller values, imposter meshes might not be an acceptable visual replacement for the model. Consider whether SLIM is a better fit for your use case.
|
||||
- Imposter meshes do not support textures; they render as smooth meshes.
|
||||
- If a model is not completely streamed in, the imposter mesh is rendered instead of individual parts of the model. After all individual parts are streamed in, they render and the imposter mesh is ignored.
|
||||
- Imposter meshes have no physical significance and are non-existent with respect to [raycasting](../workspace/raycasting.md), [collision detection](./collisions.md), and physics simulation.
|
||||
- Editing a model in Studio, such as adding, deleting, or repositioning child parts or resetting colors automatically updates the representative mesh.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Disabled** / **Automatic**</td>
|
||||
<td>The model isn't present until the player enters the streaming radius and can stream out at any time after the players leaves the radius.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Loading…
Add table
Add a link
Reference in a new issue