mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-24 14:44:07 +00:00
update rules
This commit is contained in:
parent
4c390fdd37
commit
d5522b2fef
8549 changed files with 11783 additions and 1888 deletions
241
.cursor/rules/roblox/en-us/ui/2D-paths.md
Normal file
241
.cursor/rules/roblox/en-us/ui/2D-paths.md
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
---
|
||||
title: 2D paths
|
||||
description: The Path2D instance, along with its API methods and properties, lets you implement 2D splines and 2D curved lines.
|
||||
---
|
||||
|
||||
The `Class.Path2D` instance, along with its API methods and properties, lets you implement 2D splines and 2D curved lines, useful for UI effects like path‑based animations and graph editors.
|
||||
|
||||
<video src="../assets/ui/2D-paths/Path2D.mp4" controls width="90%" alt="Video showing a UI object animating back and forth across a complex path."></video>
|
||||
|
||||
## Create a 2D path
|
||||
|
||||
To add a `Class.Path2D` to the screen or an in-experience object:
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, insert a `Class.Path2D` instance under a visible `Class.ScreenGui` or `Class.SurfaceGui` (it does not need to be a direct child).
|
||||
|
||||
<img src="../assets/studio/explorer/StarterGui-ScreenGui-Path2D.png" width="320" alt="Path2D instance parented to a ScreenGui in the Explorer hierarchy." />
|
||||
|
||||
2. Select the new `Class.Path2D` to reveal the in-viewport tooling widget. By default, the **Add Point** tool is selected.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Widget-Add-Point.png" width="600" alt="Add Point tool indicated in the 2D path editor widget." />
|
||||
|
||||
3. Begin clicking on the screen to add a series of **control points** to form a path. The initial path will likely be imprecise but you can [fine‑tune](#move-points) the position of any control point later.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Create-Basic.png" width="600" alt="Diagram illustrating an example path created using the Add Point tool to connect a series of points." />
|
||||
|
||||
<Alert severity="info">
|
||||
If you drag the mouse after clicking, [tangents](#control-point-tangents) will be created on that point. Tangents can also be procedurally [added](#add-tangents) to any control point.
|
||||
</Alert>
|
||||
|
||||
4. When finished, click the widget's **Done** button or press <kbd>Enter</kbd>.
|
||||
|
||||
## Modify control points
|
||||
|
||||
With a `Class.Path2D` selected in the [Explorer](../studio/explorer.md) hierarchy, you can modify its individual control points as well as their [tangents](#control-point-tangents).
|
||||
|
||||
### Move points
|
||||
|
||||
To move an individual control point on a path, enable the **Select** tool (<kbd>V</kbd>) and then click‑and‑drag it to a new position.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Widget-Select.png" width="600" alt="Select tool indicated in the 2D path editor widget." />
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Move-Point.png" width="600" alt="Diagram illustrating how a path control point is moved to a new position by clicking and dragging with the Select tool enabled." />
|
||||
|
||||
For very specific positioning, select the control point and then, in the [Properties](../studio/properties.md) window, set a new position for the point's `Class.Path2D.SelectedControlPointData|SelectedControlPointData` property (`Datatype.UDim2`).
|
||||
|
||||
<img src="../assets/studio/properties/Path2D-SelectedControlPointData.png" width="320" alt="SelectedControlPointData property of a Path2D instance indicated in the Properties window." />
|
||||
|
||||
Note that a point's position is not absolute, but rather **relative** to the path's parent container. For example, compare a control point 30% from left and 20% from top for a path inside a `Class.ScreenGui`, versus an identical path placed inside a `Class.Frame` located in the upper‑right corner of the `Class.ScreenGui`.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Control-Point-Relativity.png" width="864" alt="Diagram illustrating how the positions of path control points are relative to their parent container." />
|
||||
|
||||
### Add points
|
||||
|
||||
New control points can be added to a `Class.Path2D` either between two existing points or from either end point using the **Add Point** tool (<kbd>P</kbd>).
|
||||
|
||||
<img src="../assets/ui/2D-paths/Widget-Add-Point.png" width="600" alt="Add Point tool indicated in the 2D path editor widget." />
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Add-Point-Between.png" width="560" alt="Diagram illustrating how a control point is added between two existing points using the Add Point tool." />
|
||||
<figcaption>Hover anywhere over the path and click to add a control point between two existing points</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Add-Point-From-End.png" width="560" alt="Diagram illustrating how a control point is added to the end of a path using the Add Point tool." />
|
||||
<figcaption>Click on an end point and then click again to add a control point from that end</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="info">
|
||||
Remember that if you drag the mouse after clicking to add a new control point, [tangents](#control-point-tangents) will be created on that point.
|
||||
</Alert>
|
||||
|
||||
### Delete points
|
||||
|
||||
To delete a control point, hover over and right‑click it, then select **Delete Point** from the contextual popup menu.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Delete-Point.png" width="600" alt="Diagram illustrating how a path control point is deleted by right-clicking it and selecting Delete Point." />
|
||||
|
||||
### Control point tangents
|
||||
|
||||
Control point **tangents** let you create and adjust curves on a path.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Tangents.png" width="800" alt="Diagram illustrating a curved path with several tangent control points." />
|
||||
|
||||
#### Add tangents
|
||||
|
||||
To add tangents to any control point that doesn't already have tangents:
|
||||
|
||||
1. Enable the **Add Tangent** tool in the tooling widget.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Widget-Add-Tangent.png" width="600" alt="Add Tangent tool indicated in the 2D path editor widget." />
|
||||
|
||||
2. Hover over the desired control point, then click to add two [mirrored](#break-and-mirror) tangents (optionally drag after clicking to [adjust](#adjust-tangents) the new tangents).
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Add-Tangent.png" width="600" alt="Diagram illustrating how a path control point with tangents is created with the Add Tangent tool." />
|
||||
|
||||
#### Adjust tangents
|
||||
|
||||
To adjust existing tangents for an individual control point:
|
||||
|
||||
1. Enable the **Select** tool (<kbd>V</kbd>).
|
||||
|
||||
<img src="../assets/ui/2D-paths/Widget-Select.png" width="600" alt="Select tool indicated in the 2D path editor widget." />
|
||||
|
||||
2. Hover over a tangent marker (not the control point), then click‑and‑drag it to a new position. If the tangents are mirrored, the paired tangent point will move in unison.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Adjust-Tangent.png" width="800" alt="Diagram illustrating how the tangents of a control point are adjusted with the Select tool enabled." />
|
||||
|
||||
To manually set a specific `Datatype.UDim2` position for a tangent:
|
||||
|
||||
1. Enable the **Select** tool (<kbd>V</kbd>) and select the control point.
|
||||
In the [Properties](../studio/properties.md) window, expand the `Class.Path2D.SelectedControlPointData|SelectedControlPointData` field to expose the `Datatype.Path2DControlPoint.LeftTangent|LeftTangent` and `Datatype.Path2DControlPoint.RightTangent|RightTangent` properties.
|
||||
|
||||
<img src="../assets/studio/properties/Path2D-SelectedControlPointData-Expanded.png" width="320" alt="LeftTangent and RightTangent properties of a Path2D control point indicated in the Properties window." />
|
||||
|
||||
2. Set the position for `Datatype.Path2DControlPoint.LeftTangent|LeftTangent` and/or `Datatype.Path2DControlPoint.RightTangent|RightTangent`. Note that this will [break the mirrored behavior](#break-and-mirror) of the tangents.
|
||||
|
||||
#### Delete tangents
|
||||
|
||||
To delete both tangents from a control point, hover over and right-click that point, then select **Clear Tangents** from the contextual popup menu.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Clear-Tangents.png" width="800" alt="Diagram illustrating how both tangents of a control point are cleared by right-clicking it and selecting Clear Tangents." />
|
||||
|
||||
To delete just one of the tangents (left or right), hover over and right‑click that tangent's marker, then select **Delete Tangent**.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Delete-Tangent.png" width="800" alt="Diagram illustrating how one tangent of a control point is deleted by right-clicking it and selecting Delete Tangent." />
|
||||
|
||||
#### Break and mirror
|
||||
|
||||
By default, tangents mirror each other. When you [drag to adjust](#adjust-tangents) one tangent marker, the paired tangent point will move in unison.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Adjust-Tangent.png" width="800" alt="Diagram illustrating how the tangents of a control point are adjusted with the Select tool enabled." />
|
||||
|
||||
To "break" the tangents so that each can be moved independently of the other, hover over and right‑click the associated control point, then select **Break Tangents** from the contextual menu. Once broken, you can move each tangent marker without affecting the other.
|
||||
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Break-Tangents.png" width="800" alt="Diagram illustrating how the tangents of a control point are broken by right-clicking it and selecting Break Tangents." />
|
||||
|
||||
<Alert severity="info">
|
||||
To re-establish mirror behavior, hover over and right‑click the associated control point, then select **Mirror Tangents** from the contextual menu. Note that you will **not** see an immediate change in the path after re‑establishing mirror behavior, as the system cannot predict which tangent point you want to begin mirroring. Mirror behavior will resume only once you [drag to adjust](#adjust-tangents) one of the tangent markers.
|
||||
</Alert>
|
||||
|
||||
## Path visual properties
|
||||
|
||||
You can customize the visual appearance of a `Class.Path2D` with the following properties:
|
||||
|
||||
<table size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Path2D.Color3|Color3`</td>
|
||||
<td>Sets the color of the path line.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Path2D.Thickness|Thickness`</td>
|
||||
<td>Sets the thickness of the path line, in pixels.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Path2D.Visible|Visible`</td>
|
||||
<td>Make the path visible or not in both edit and runtime.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Path2D.ZIndex|ZIndex`</td>
|
||||
<td>Order in which a path renders relative to other GUIs.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Thin-Grey.png" width="600" alt="Diagram illustrating a path with Thickness of 2 and Color3 of (125, 125, 125)." />
|
||||
<figcaption>Thickness = 2<br />Color3 = (125, 125, 125)</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/2D-paths/Path-Example-Thick-Red.png" width="600" alt="Diagram illustrating a path with Thickness of 10 and Color3 of (225, 0, 50)." />
|
||||
<figcaption>Thickness = 10<br />Color3 = (225, 0, 50)</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/2D-paths/Path-Example-ZIndex-Layering.png" width="600" alt="Diagram illustrating a path layered in front of another using the ZIndex property." />
|
||||
<figcaption>ZIndex layering</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Path scripting
|
||||
|
||||
Scripting is useful for several path-related workflows. The following examples use methods such as `Class.Path2D:GetControlPoints()|GetControlPoints()` which returns a table of `Datatype.Path2DControlPoint|Path2DControlPoints` and `Class.Path2D:GetPositionOnCurveArcLength()|GetPositionOnCurveArcLength()` which returns the `Datatype.UDim2` position at a given `t` value along the spline.
|
||||
|
||||
```lua title="Arrange UI Objects Across Path"
|
||||
local parent = script.Parent
|
||||
local path = parent:FindFirstChildWhichIsA("Path2D")
|
||||
|
||||
local function arrangeChildren()
|
||||
local segmentCount = #path:GetControlPoints()
|
||||
local objectsToArrange = {}
|
||||
|
||||
for _, child in parent:GetChildren() do
|
||||
if child:IsA("GuiObject") then
|
||||
table.insert(objectsToArrange, child)
|
||||
end
|
||||
end
|
||||
|
||||
for idx, child in objectsToArrange do
|
||||
local t = idx / (#objectsToArrange + 1)
|
||||
child.Position = path:GetPositionOnCurveArcLength(t)
|
||||
end
|
||||
end
|
||||
|
||||
-- Initially arrange child UI objects across path
|
||||
arrangeChildren()
|
||||
|
||||
-- Listen for children being added/removed to adjust arrangement
|
||||
parent.ChildAdded:Connect(arrangeChildren)
|
||||
parent.ChildRemoved:Connect(arrangeChildren)
|
||||
```
|
||||
|
||||
```lua title="Animate UI Object Along Path"
|
||||
local Tweenservice = game:GetService("TweenService")
|
||||
|
||||
local parent = script.Parent
|
||||
local path = parent:FindFirstChildWhichIsA("Path2D")
|
||||
local objectToAnimate = parent:FindFirstChildWhichIsA("GuiObject")
|
||||
|
||||
local TWEEN_DURATION = 4
|
||||
local TWEEN_EASING_STYLE = Enum.EasingStyle.Cubic
|
||||
local TWEEN_EASING_DIRECTION = Enum.EasingDirection.InOut
|
||||
|
||||
local pathSampleValue = Instance.new("NumberValue")
|
||||
local tweenInfo = TweenInfo.new(TWEEN_DURATION, TWEEN_EASING_STYLE, TWEEN_EASING_DIRECTION, 0, true, 2)
|
||||
local tween = Tweenservice:Create(pathSampleValue, tweenInfo, {Value = 1})
|
||||
|
||||
local function onSampleValueChanged()
|
||||
objectToAnimate.Position = path:GetPositionOnCurveArcLength(pathSampleValue.Value)
|
||||
end
|
||||
pathSampleValue.Changed:Connect(onSampleValueChanged)
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
493
.cursor/rules/roblox/en-us/ui/3D-drag-detectors.md
Normal file
493
.cursor/rules/roblox/en-us/ui/3D-drag-detectors.md
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
---
|
||||
title: 3D drag detectors
|
||||
description: 3D drag detectors facilitate and encourage interaction with 3D objects in an experience, such as opening doors and drawers, sliding a part around, and much more.
|
||||
---
|
||||
|
||||
The `Class.DragDetector` instance facilitates and encourages interaction with 3D objects in an experience, such as opening doors and drawers, sliding a part around, grabbing and tossing a bowling ball, pulling back and firing a slingshot, and much more. Key features include:
|
||||
|
||||
- Place a `Class.DragDetector` under any `Class.BasePart` or `Class.Model` to [make it draggable](#make-objects-draggable) via all inputs (mouse, touch, gamepad, and VR), all without a single line of code.
|
||||
|
||||
- Choose from several [drag styles](#drag-style), define how the object [responds to motion](#response-to-motion), and optionally apply [axis or movement limits](#axis--movement-limits).
|
||||
|
||||
- Scripts can [respond to manipulation of dragged objects](#script-responses-to-clicking-and-dragging) to drive UI or make logical decisions, such as adjusting the light level in a room based on a sliding wall switch dimmer.
|
||||
|
||||
- Players can manipulate anchored parts or models and they'll stay exactly where you put them upon release.
|
||||
|
||||
- `Class.DragDetector|DragDetectors` work in Studio as long as you're **not** using the **Select**, **Move**, **Scale**, or **Rotate** tools, making it easier to test and adjust draggable objects while editing.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Showcase.mp4" controls width="100%" alt="Drag detectors used in a variety of implementations in the 3D world"></video>
|
||||
|
||||
## Make objects draggable
|
||||
|
||||
To make any part or model draggable, simply add a `Class.DragDetector` as a direct descendant.
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, hover over the `Class.Part`, `Class.MeshPart`, or `Class.Model` and click the ⊕ button. A contextual menu displays.
|
||||
1. From the menu, insert a **DragDetector**.
|
||||
|
||||
<img src="../assets/studio/explorer/Model-DragDetector.png" width="320" />
|
||||
|
||||
1. By default, the object will now be draggable in the ground plane, but you can customize its `Class.DragDetector.DragStyle|DragStyle`, define how it [responds to motion](#response-to-motion), and optionally apply [axis or movement limits](#axis--movement-limits).
|
||||
|
||||
<Alert severity="warning">
|
||||
Remember that `Class.DragDetector|DragDetectors` only work in Studio if you're **not** using the **Select**, **Move**, **Scale**, or **Rotate** tools.
|
||||
</Alert>
|
||||
|
||||
## Customize drag detectors
|
||||
|
||||
### Drag style
|
||||
|
||||
`Class.DragDetector|DragDetectors` map cursor motion to virtual lines and planes to calculate proposed 3D motion. Through the `Class.DragDetector.DragStyle|DragStyle` property, you can choose from different mappings to suit your needs. For example, **TranslatePlane** produces translation in a virtual plane, whereas **RotateAxis** produces rotation about a virtual axis.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.TranslateLine|TranslateLine`</td>
|
||||
<td>1D motion along the detector's `Class.DragDetector.Axis|Axis`, by default the world **Y** axis.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.TranslatePlane|TranslatePlane`</td>
|
||||
<td>2D motion in the plane perpendicular to the detector's `Class.DragDetector.Axis|Axis`, by default the world **XZ** plane.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.TranslatePlaneOrLine|TranslatePlaneOrLine`</td>
|
||||
<td>2D motion in the plane perpendicular to the detector's `Class.DragDetector.Axis|Axis` and, when the [modifier](#modifier-input) is active, 1D motion along the detector's `Class.DragDetector.Axis|Axis`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.TranslateLineOrPlane|TranslateLineOrPlane`</td>
|
||||
<td>1D motion along the detector's `Class.DragDetector.Axis|Axis` and, when the [modifier](#modifier-input) is active, 2D motion in the plane perpendicular to the detector's `Class.DragDetector.Axis|Axis`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.TranslateViewPlane|TranslateViewPlane`</td>
|
||||
<td>2D motion in the plane perpendicular to the camera's view. In this mode, the plane is constantly updated, even while dragging, and will always face the camera's current view.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.RotateAxis|RotateAxis`</td>
|
||||
<td>Rotation about the detector's `Class.DragDetector.Axis|Axis`, by default the world **Y** axis.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.RotateTrackball|RotateTrackball`</td>
|
||||
<td>Trackball rotation, further customized through the `Class.DragDetector.TrackballRadialPullFactor|TrackballRadialPullFactor` and `Class.DragDetector.TrackballRollFactor|TrackballRollFactor` properties.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.BestForDevice|BestForDevice`</td>
|
||||
<td>**TranslatePlaneOrLine** for mouse and gamepad; **TranslatePlane** for touch; **6DOF** for VR.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorDragStyle.Scriptable|Scriptable`</td>
|
||||
<td>Calculates desired motion via a custom function provided through `Class.DragDetector:SetDragStyleFunction()|SetDragStyleFunction()`.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Drag direction
|
||||
|
||||
By default, 3D motion and the associated `Class.DragDetector.DragStyle|DragStyle` map to world space. However, you may want to change the `Class.DragDetector.ReferenceInstance|ReferenceInstance`, `Class.DragDetector.Orientation|Orientation`, or `Class.DragDetector.Axis|Axis`, for example when building drag detectors into [models with adjustable parts](#anchored-models-with-adjustable-parts).
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.ReferenceInstance|ReferenceInstance`</td>
|
||||
<td>An instance whose pivot provides the **reference frame** for the drag detector. The `Class.DragDetector.DragFrame|DragFrame` is expressed relative to this reference frame which may be retrieved via `Class.DragDetector:GetReferenceFrame()|GetReferenceFrame()`. If the reference frame is `nil`, translation will be in the direction of (or in the plane perpendicular to) the `Class.DragDetector.Axis|Axis` property in world space.</td>
|
||||
<td>`nil`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.Orientation|Orientation`</td>
|
||||
<td>Specifies the **YXZ** rotation of axes of motion relative to the reference frame (does not change the orientation of the reference frame itself). Linear translation and axial rotation will be on this reoriented **Y** axis, and planar translation in the **XZ** plane. Changing this value automatically updates `Class.DragDetector.Axis|Axis` and vice versa.</td>
|
||||
<td>(0, 0, 0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.Axis|Axis`</td>
|
||||
<td>The primary axis of motion, expressed relative to the reference frame. Changing this value automatically updates `Class.DragDetector.Orientation|Orientation` and vice versa.</td>
|
||||
<td>(0, 1, 0)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Response to motion
|
||||
|
||||
The `Class.DragDetector.ResponseStyle|ResponseStyle` property specifies how an object responds to the proposed motion, depending on whether the object is `Class.BasePart.Anchored|Anchored` or not.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Anchored behavior</th>
|
||||
<th>Unanchored behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorResponseStyle.Geometric|Geometric`</td>
|
||||
<td>Both inside the running experience and in Studio edit mode, the position/orientation of an anchored object will be updated to exactly reflect the proposed motion.</td>
|
||||
<td>For an unanchored object, behavior is the same as for an anchored object. However, in a running experience, the object will be anchored at the start of the drag and restored to unanchored upon drag release.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorResponseStyle.Physical|Physical`</td>
|
||||
<td>An anchored object will default to **Geometric** behavior, as it is not affected by forces.</td>
|
||||
<td>An unanchored object will be moved by [constraint forces](#physics-response) that attempt to bring it to the desired position and/or orientation given by the proposed motion. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorResponseStyle.Custom|Custom`</td>
|
||||
<td>The object will not move at all, but `Class.DragDetector.DragFrame|DragFrame` will still be updated and you can [respond to drag manipulation](#script-responses-to-clicking-and-dragging) however you'd like.</td>
|
||||
<td>(same as anchored)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="warning">
|
||||
Remember that `Class.DragDetector|DragDetectors` only work in Studio if you're **not** using the **Select**, **Move**, **Scale**, or **Rotate** tools.
|
||||
</Alert>
|
||||
|
||||
### Axis & movement limits
|
||||
|
||||
By default, there are no limits to 3D motion beyond the inherent restrictions of the `Class.DragDetector.DragStyle|DragStyle`. If necessary, you can apply minimum and maximum limits to both translation and rotation. Note, however, that these are **not** constraints; they merely impede the drag detector's attempts to generate motion in order to remain within limits.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Properties</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.MinDragTranslation|MinDragTranslation`<br />
|
||||
`Class.DragDetector.MaxDragTranslation|MaxDragTranslation`</td>
|
||||
<td>Limits to drag translation in each dimension. If MaxDragTranslation is greater than MinDragTranslation, translation will be clamped within that range.</td>
|
||||
<td>(0, 0, 0)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.MinDragAngle|MinDragAngle`<br />
|
||||
`Class.DragDetector.MaxDragAngle|MaxDragAngle`</td>
|
||||
<td>Only relevant if `Class.DragDetector.DragStyle|DragStyle` is set to **RotateAxis**. If `Class.DragDetector.MaxDragAngle|MaxDragAngle` is greater than `Class.DragDetector.MinDragAngle|MinDragAngle`, rotation will be clamped within that range.</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="warning">
|
||||
When using axis/movement limits, you should always set the detector's `Class.DragDetector.ReferenceInstance|ReferenceInstance` so that the limits are relative to a dedicated reference frame. If you fail to establish the reference frame, each drag of the object will reset its limits to its own current world space position/orientation.
|
||||
</Alert>
|
||||
|
||||
### Drag permissions
|
||||
|
||||
Permission of players to interact with a given drag detector instance can be specified by the `Class.DragDetector.PermissionPolicy|PermissionPolicy` property. This is set to `Enum.DragDetectorPermissionPolicy.Everybody` by default, and it can also be changed to support scripted permission controls as shown in the code sample.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorPermissionPolicy.Nobody|Nobody`</td>
|
||||
<td>No players can interact with the `Class.DragDetector`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorPermissionPolicy.Everybody|Everybody`</td>
|
||||
<td>All players can interact with the `Class.DragDetector`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.DragDetectorPermissionPolicy.Scriptable|Scriptable`</td>
|
||||
<td>Players' drag permissions will be determined by a function registered through `Class.DragDetector:SetPermissionPolicyFunction()|SetPermissionPolicyFunction()`. Under this setting, failure to register a function or returning an invalid result will prevent all players from dragging.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```lua title="DragDetector - Scripted Drag Permission"
|
||||
local dragDetector = script.Parent.DragDetector
|
||||
dragDetector.PermissionPolicy = Enum.DragDetectorPermissionPolicy.Scriptable
|
||||
|
||||
dragDetector:SetPermissionPolicyFunction(function(player, part)
|
||||
if player and player:GetAttribute("IsInTurn") then
|
||||
return true
|
||||
elseif part and not part:GetAttribute("IsDraggable") then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
||||
### Physics response
|
||||
|
||||
Assuming a dragger's [response style](#response-to-motion) is set to **Physical** and it is applied to an unanchored object, that object will be moved by constraint forces that attempt to bring it to the position/orientation given by the proposed motion. You can further customize the physical response through the following properties:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.ApplyAtCenterOfMass|ApplyAtCenterOfMass`</td>
|
||||
<td>When false, drag force is applied at the point the user clicks on. When true, force is applied at the object's center of mass.</td>
|
||||
<td>false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.MaxForce|MaxForce`</td>
|
||||
<td>Maximum force applied for the object to reach its goal.</td>
|
||||
<td>10000000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.MaxTorque|MaxTorque`</td>
|
||||
<td>Maximum torque applied for the object to reach its goal.</td>
|
||||
<td>10000</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.Responsiveness|Responsiveness`</td>
|
||||
<td>Higher values cause the object to reach its goal more rapidly.</td>
|
||||
<td>10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Modifier input
|
||||
|
||||
Some `Class.DragDetector.DragStyle|DragStyle` modes allow users to hold down a **modifier** key/button to manipulate the dragged object in different ways. By default, the modifier is `Enum.KeyCode|LeftControl` on PC, `Enum.KeyCode|ButtonR1` on gamepad, or `Enum.KeyCode|ButtonL2` on VR. You can customize these modifiers through the `Class.DragDetector.KeyboardModeSwitchKeyCode|KeyboardModeSwitchKeyCode`, `Class.DragDetector.GamepadModeSwitchKeyCode|GamepadModeSwitchKeyCode`, or `Class.DragDetector.VRSwitchKeyCode|VRSwitchKeyCode` properties of the drag detector instance.
|
||||
|
||||
### Replication
|
||||
|
||||
When the `Class.DragDetector.RunLocally|RunLocally` property is false (default), the client interprets all input to produce data that it sends to the server to perform the drag. In this mode, all custom event signals and registered functions must be in server `Class.Script|Scripts`.
|
||||
|
||||
When the `Class.DragDetector.RunLocally|RunLocally` property is true, no events are replicated to the server. All custom event signals and registered functions must be in client `Class.LocalScript|LocalScripts` and you must use [remote events](../scripting/events/remote.md) to propagate necessary changes to the server.
|
||||
|
||||
## Script responses to clicking and dragging
|
||||
|
||||
Through [event signals](#event-signals), property changes, `Enum.DragDetectorDragStyle.Scriptable|Scriptable` drag style, and custom functions, scripts can respond to the manipulation of dragged objects to drive UI or make logical decisions, such as adjusting the light level in a room based on a sliding wall switch dimmer.
|
||||
|
||||
### Event signals
|
||||
|
||||
Through the following event signals, you can detect when a user starts, continues, and ends dragging an object.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.DragStart|DragStart`</td>
|
||||
<td>Fires when a user starts dragging the object.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.DragContinue|DragContinue`</td>
|
||||
<td>Fires when a user continues dragging the object after `Class.DragDetector.DragStart|DragStart` has been initiated.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.DragDetector.DragEnd|DragEnd`</td>
|
||||
<td>Fires when a user stops dragging the object.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```lua title="DragDetector - Event Signals" highlight="7,9,11,13,15,17"
|
||||
local dragDetector = script.Parent.DragDetector
|
||||
|
||||
local highlight = Instance.new("Highlight")
|
||||
highlight.Enabled = false
|
||||
highlight.Parent = script.Parent
|
||||
|
||||
dragDetector.DragStart:Connect(function()
|
||||
highlight.Enabled = true
|
||||
end)
|
||||
|
||||
dragDetector.DragContinue:Connect(function()
|
||||
|
||||
end)
|
||||
|
||||
dragDetector.DragEnd:Connect(function()
|
||||
highlight.Enabled = false
|
||||
end)
|
||||
```
|
||||
|
||||
### Drag frame changes
|
||||
|
||||
In addition to [event signals](#event-signals), you can monitor changes to the detector's `Class.DragDetector.DragFrame|DragFrame` directly.
|
||||
|
||||
```lua title="DragDetector - DragFrame Changes" highlight="3,6"
|
||||
local dragDetector = script.Parent.DragDetector
|
||||
|
||||
dragDetector:GetPropertyChangedSignal("DragFrame"):Connect(function()
|
||||
local currentDragTranslation = dragDetector.DragFrame.Position
|
||||
print(currentDragTranslation)
|
||||
end)
|
||||
```
|
||||
|
||||
### Scripted drag style
|
||||
|
||||
If you set a detector's `Class.DragDetector.DragStyle|DragStyle` to **Scriptable**, you can provide your own function that takes in a `Datatype.Ray` and returns a world space `Datatype.CFrame`. The detector will move the motion so that the dragged object goes to that custom location/orientation.
|
||||
|
||||
```lua title="DragDetector - Scripted DragStyle" highlight="2,7,31,32,34"
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local dragDetector = script.Parent.DragDetector
|
||||
dragDetector.DragStyle = Enum.DragDetectorDragStyle.Scriptable
|
||||
|
||||
local cachedHitPoint = Vector3.zero
|
||||
local cachedHitNormal = Vector3.yAxis
|
||||
|
||||
local function followTheCursor(cursorRay)
|
||||
-- Exclude dragged object from raycast detection
|
||||
local raycastParams = RaycastParams.new()
|
||||
raycastParams.FilterDescendantsInstances = {dragDetector.Parent}
|
||||
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
|
||||
|
||||
local hitPoint = Vector3.zero
|
||||
local hitNormal = Vector3.yAxis
|
||||
|
||||
local raycastResult = Workspace:Raycast(cursorRay.Origin, cursorRay.Direction, raycastParams)
|
||||
if raycastResult then
|
||||
hitPoint = raycastResult.Position
|
||||
hitNormal = raycastResult.Normal.Unit
|
||||
else
|
||||
hitPoint = cachedHitPoint
|
||||
hitNormal = cachedHitNormal
|
||||
end
|
||||
|
||||
cachedHitPoint = hitPoint
|
||||
cachedHitNormal = hitNormal
|
||||
|
||||
local lookDir1 = hitNormal:Cross(Vector3.xAxis)
|
||||
local lookDir2 = hitNormal:Cross(Vector3.yAxis)
|
||||
local lookDir = if lookDir1.Magnitude > lookDir2.Magnitude then lookDir1.Unit else lookDir2.Unit
|
||||
return CFrame.lookAt(hitPoint, hitPoint + lookDir, hitNormal)
|
||||
end
|
||||
|
||||
dragDetector:SetDragStyleFunction(followTheCursor)
|
||||
```
|
||||
|
||||
### Custom constraint function
|
||||
|
||||
Drag detectors do not have built-in motion rules about grids and snapping, but you can register custom constraint functions to edit the detector's `Class.DragDetector.DragFrame|DragFrame` before it is applied. For example, you can keep motion on a grid by rounding positions to multiples of the grid increment, or simulate a chess game with rules of motion legal to each piece.
|
||||
|
||||
```lua title="DragDetector - Custom Constraint Function" highlight="4,14,27,28,30"
|
||||
local dragDetector = script.Parent.DragDetector
|
||||
local startPartPosition = nil
|
||||
|
||||
local SNAP_INCREMENT = 4
|
||||
|
||||
dragDetector.DragStart:Connect(function()
|
||||
startPartPosition = script.Parent.Position
|
||||
end)
|
||||
|
||||
dragDetector.DragEnd:Connect(function()
|
||||
startPartPosition = nil
|
||||
end)
|
||||
|
||||
local function snapToWorldGrid(proposedMotion)
|
||||
if startPartPosition == nil then
|
||||
return proposedMotion
|
||||
end
|
||||
local snapIncrement = SNAP_INCREMENT // 1
|
||||
if snapIncrement < 1 then
|
||||
return proposedMotion
|
||||
end
|
||||
local newWorldPosition = startPartPosition + proposedMotion.Position
|
||||
local roundedX = ((newWorldPosition.X / snapIncrement + 0.5) // 1) * snapIncrement
|
||||
local roundedY = ((newWorldPosition.Y / snapIncrement + 0.5) // 1) * snapIncrement
|
||||
local roundedZ = ((newWorldPosition.Z / snapIncrement + 0.5) // 1) * snapIncrement
|
||||
local newRoundedWorldPosition = Vector3.new(roundedX, roundedY, roundedZ)
|
||||
return proposedMotion.Rotation + (newRoundedWorldPosition - startPartPosition)
|
||||
end
|
||||
|
||||
local connection = dragDetector:AddConstraintFunction(2, snapToWorldGrid)
|
||||
-- When applicable, remove the constraint function by invoking connection:Disconnect()
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
### Unanchored physical objects
|
||||
|
||||
A basic implementation of drag detectors is a tower balance game where players must carefully remove pieces and attempt to keep the tower upright. In the following tower structure, each piece has a child `Class.DragDetector` with a default `Class.DragDetector.DragStyle|DragStyle` of **TranslatePlane** so that players can pull the pieces outward but not upward or downward.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Balance-Tower.mp4" controls width="800" alt="Drag detectors placed on each piece of a tower balance game"></video>
|
||||
|
||||
### Anchored models with adjustable parts
|
||||
|
||||
You can easily create and share models which are primarily anchored, but which have one or more child parts/models that players can drag. For example, the following desk has two drawers which players can open to inspect what's inside.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Desk-Drawers.mp4" controls width="800" alt="Drag detectors placed on the drawers of a desk to allow for opening/sliding"></video>
|
||||
|
||||
<Alert severity="warning">
|
||||
When making children of models draggable, you should set the drag detector's `Class.DragDetector.ReferenceInstance|ReferenceInstance` to an object within the model that can serve as a dedicated reference frame, for example the desk's top surface. This establishes a consistent reference `Datatype.CFrame` for the style/direction of dragging, even if the model is rotated.
|
||||
</Alert>
|
||||
|
||||
### Drag detectors and constraints
|
||||
|
||||
You can combine drag detectors with `Class.Constraint|Constraints`, for example a marionette puppet. In the following setup, the control handles are anchored, the body parts are unanchored, and constraints hold the marionette together. Moving the handles with the **TranslateViewPlane** `Class.DragDetector.DragStyle|DragStyle` makes the marionette dance, and the individual body parts may also be moved with drag detectors, all while the model retains its integrity.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Marionette.mp4" controls width="800" alt="Drag detectors placed on the handles of a marionette puppet"></video>
|
||||
|
||||
### 3D user interfaces
|
||||
|
||||
3D user interfaces are easily achievable through drag detectors, such as adjusting the brightness of a `Class.SpotLight` based on a sliding switch dimmer. You can also detect the **X** and **Z** axes individually to control two different aspects of a 3D user interface, such as the `Class.ParticleEmitter.Size|Size`, `Class.ParticleEmitter.Speed|Speed`, and `Class.ParticleEmitter.Color|Color` of a `Class.ParticleEmitter`.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Color-Slider.mp4" controls width="800" alt="Drag detectors used in 3D user interface to control a particle emitter"></video>
|
||||
|
||||
```lua title="DragDetector - 3D User Interface"
|
||||
local model = script.Parent
|
||||
local slider = model.SliderPart
|
||||
local originPart = model.OriginPart
|
||||
local emitter = script.Parent.EmitterPart.ParticleEmitter
|
||||
|
||||
local dragDetector = slider.DragDetector
|
||||
dragDetector.ReferenceInstance = originPart
|
||||
dragDetector.MinDragTranslation = Vector3.zero
|
||||
dragDetector.MaxDragTranslation = Vector3.new(10, 0, 10)
|
||||
|
||||
local dragRangeX = dragDetector.MaxDragTranslation.X - dragDetector.MinDragTranslation.X
|
||||
local dragRangeZ = dragDetector.MaxDragTranslation.Z - dragDetector.MinDragTranslation.Z
|
||||
|
||||
local MIN_PARTICLE_SIZE = 1
|
||||
local MAX_PARTICLE_SIZE = 1.5
|
||||
local MIN_PARTICLE_SPEED = 2.5
|
||||
local MAX_PARTICLE_SPEED = 5
|
||||
local COLOR1 = Color3.fromRGB(255, 150, 0)
|
||||
local COLOR2 = Color3.fromRGB(255, 0, 50)
|
||||
|
||||
local function updateParticles(emitter)
|
||||
local dragFactorX = (dragDetector.DragFrame.Position.X - dragDetector.MinDragTranslation.X) / dragRangeX
|
||||
local dragFactorZ = (dragDetector.DragFrame.Position.Z - dragDetector.MinDragTranslation.Z) / dragRangeZ
|
||||
|
||||
-- Adjust particle size and speed based on drag detector X factor
|
||||
emitter.Size = NumberSequence.new{
|
||||
NumberSequenceKeypoint.new(0, 0),
|
||||
NumberSequenceKeypoint.new(0.1, MIN_PARTICLE_SIZE + ((MAX_PARTICLE_SIZE - MIN_PARTICLE_SIZE) * dragFactorX)),
|
||||
NumberSequenceKeypoint.new(1, 0)
|
||||
}
|
||||
local speed = MIN_PARTICLE_SPEED + ((MAX_PARTICLE_SPEED - MIN_PARTICLE_SPEED) * dragFactorX)
|
||||
emitter.Speed = NumberRange.new(speed, speed * 1.2)
|
||||
-- Adjust particle color based on drag detector Z factor
|
||||
local color = COLOR2:Lerp(COLOR1, dragFactorZ)
|
||||
emitter.Color = ColorSequence.new{
|
||||
ColorSequenceKeypoint.new(0, color),
|
||||
ColorSequenceKeypoint.new(1, color)
|
||||
}
|
||||
end
|
||||
|
||||
dragDetector:GetPropertyChangedSignal("DragFrame"):Connect(function()
|
||||
updateParticles(emitter)
|
||||
end)
|
||||
```
|
||||
113
.cursor/rules/roblox/en-us/ui/9-slice.md
Normal file
113
.cursor/rules/roblox/en-us/ui/9-slice.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
title: UI 9-slice design
|
||||
description: UI 9-slice design lets you create UI elements of varying sizes without distorting the borders or corners.
|
||||
---
|
||||
|
||||
When creating UI with custom borders and corners, you'll often need to render elements at different aspect ratios and visually surround [localized text](../production/localization/index.md) or other contents of unknown dimensions.
|
||||
This lets you create UI elements of varying sizes without distorting the borders or corners.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Comparison-Without.png" />
|
||||
<figcaption>Without 9-Slice</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Comparison-With.png" />
|
||||
<figcaption>With 9-Slice</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
Under the **9-slice** approach, one image (a single Roblox image asset) is
|
||||
internally divided into nine sub-images, each with different scaling rules.
|
||||
<img src="../assets/ui/9-slice/9-Slice-Concept-Diagram.png" width="412" />
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sub-image</th>
|
||||
<th>Scaling</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>1</strong> <strong>3</strong> <strong>7</strong> <strong>9</strong> (corners)</td>
|
||||
<td>none</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>2</strong> <strong>8</strong> (top/bottom edges)</td>
|
||||
<td>horizontal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>4</strong> <strong>6</strong> (left/right edges)</td>
|
||||
<td>vertical</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>5</strong> (center)</td>
|
||||
<td>horizontal + vertical</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Studio editor
|
||||
|
||||
Slice configuration is possible by directly setting the `Enum.ScaleType` and slice properties on an image label or button, but Studio's built-in **9-Slice Editor** is more intuitive.
|
||||
|
||||
### Opening the editor
|
||||
|
||||
To open the visual **9-Slice Editor** in Studio:
|
||||
|
||||
1. Select an `Class.ImageLabel` or `Class.ImageButton` with a valid asset ID entered into its **Image** field.
|
||||
|
||||
<img src="../assets/ui/9-slice/9-Slice-Properties-Image.png" width="320" />
|
||||
|
||||
1. Set the **ScaleType** property to **Slice**.
|
||||
|
||||
<img src="../assets/ui/9-slice/9-Slice-Properties-ScaleType.png" width="320" />
|
||||
|
||||
1. Click on the **SliceCenter** property that appears, then click on the **…** within the row.
|
||||
|
||||
<img src="../assets/ui/9-slice/9-Slice-Properties-SliceCenter.png" width="320" />
|
||||
|
||||
1. In the window that opens, 4 red lines overlay the image, representing the edges of the slices. The source image size is also displayed.
|
||||
|
||||
<img src="../assets/ui/9-slice/9-Slice-Editor-View.png" width="327" />
|
||||
|
||||
### Set offsets
|
||||
|
||||
In Studio, drag the red lines to set the **offsets** from the left, right, top,
|
||||
and bottom edges of the image.
|
||||
|
||||
<Alert severity="info">
|
||||
If you need better precision, enter offset values
|
||||
in the fields on the right side of the editor window.
|
||||
</Alert>
|
||||
|
||||
<img src="../assets/ui/9-slice/9-Slice-Offset-Diagram.png" width="412" />
|
||||
|
||||
As you reposition the dragger lines, the UI element automatically updates to show the result.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Example-A-Editor.png" />
|
||||
<figcaption>Slice setup</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Example-A-ImageLabel.png" />
|
||||
<figcaption>Result on 500×200 ImageLabel</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Example-B-Editor.png" />
|
||||
<figcaption>Slice setup</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/9-slice/9-Slice-Example-B-ImageLabel.png" />
|
||||
<figcaption>Result on 500×200 ImageLabel</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="warning">
|
||||
The offset values are different from the UI object's <strong>SliceCenter</strong> property values and represent the number of pixels between an edge of the source image and the dragger associated with that side.
|
||||
</Alert>
|
||||
595
.cursor/rules/roblox/en-us/ui/animation.md
Normal file
595
.cursor/rules/roblox/en-us/ui/animation.md
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
---
|
||||
title: UI animation/tweens
|
||||
description: Explains how to animate GuiObjects using the process of tweening.
|
||||
---
|
||||
|
||||
In animation, **tweening** is the process of generating intermediate frames between two key points in a sequence. When designing a user interface, you can use tweening to transition a `Class.GuiObject` smoothly from one state to another, such as:
|
||||
|
||||
- Smoothly increasing the size of a button when a user selects it.
|
||||
- Sliding UI menus in and out from the screen edges.
|
||||
- Gradually animating a health bar between two widths when a user receives a health boost.
|
||||
|
||||
## Single-property tweens
|
||||
|
||||
### Position
|
||||
|
||||
To tween the **position** of a `Class.GuiObject`:
|
||||
|
||||
1. Set the `Class.GuiObject.AnchorPoint|AnchorPoint` for the object.
|
||||
1. Determine `Datatype.UDim2` coordinates for the object's target position, using the **scale** parameters of `Datatype.UDim2` instead of exact pixel values so that the object tweens to the exact center of the screen.
|
||||
1. Pass a `Datatype.TweenInfo` and the target position to `Class.TweenService:Create()`.
|
||||
1. Play the tween with `Class.Tween:Play()`.
|
||||
|
||||
The following code snippet moves an `Class.ImageLabel` within a `Class.ScreenGui` to the exact center of the screen:
|
||||
|
||||
```lua title="UI Tween - Position" highlight="8,10,12,13,15"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local targetPosition = UDim2.new(0.5, 0, 0.5, 0)
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Position = targetPosition})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
To tween the **size** of a `Class.GuiObject`:
|
||||
|
||||
1. Determine `Datatype.UDim2` coordinates for the object's target size, using the **scale** parameters of `Datatype.UDim2` instead of exact pixel values so that the object tweens to a relative percentage of the screen size.
|
||||
1. Attach a `Class.UIAspectRatioConstraint` to the object to maintain its designed aspect ratio when tweening.
|
||||
1. Pass a `Datatype.TweenInfo` and the target size to `Class.TweenService:Create()`.
|
||||
1. Play the tween with `Class.Tween:Play()`.
|
||||
|
||||
The following code snippet scales an `Class.ImageLabel` within a `Class.ScreenGui` to 40% of the screen width or height (whichever is smaller) from the object's center anchor point:
|
||||
|
||||
```lua title="UI Tween - Size" highlight="8,10,11,13,16,18"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
|
||||
aspectRatioConstraint.Parent = object
|
||||
|
||||
local targetSize = UDim2.new(0.4, 0, 0.4, 0)
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Size = targetSize})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
### Rotation
|
||||
|
||||
To tween the **rotation** of a `Class.GuiObject`:
|
||||
|
||||
1. Set the `Class.GuiObject.AnchorPoint|AnchorPoint` for the object to rotate around.
|
||||
1. Determine the target `Class.GuiObject.Rotation|Rotation` for the object.
|
||||
1. Pass a `Datatype.TweenInfo` and the target rotation to `Class.TweenService:Create()`.
|
||||
1. Play the tween with `Class.Tween:Play()`.
|
||||
|
||||
```lua title="UI Tween - Size" highlight="8,10,13,15"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local targetRotation = 45
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Rotation = targetRotation})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
### Transparency
|
||||
|
||||
Multiple properties control UI transparency, depending on the object type. You can tween each of these properties individually or combined through a [multi-property tween](#multi-property-tweens). Alternatively, you can tween an object's overall transparency by placing it inside a `Class.CanvasGroup` and tweening the group's `Class.CanvasGroup.GroupTransparency|GroupTransparency`.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>UI Object</th>
|
||||
<th>Properties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Frame`</td>
|
||||
<td>`Class.GuiObject.BackgroundTransparency|BackgroundTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextLabel`</td>
|
||||
<td>`Class.GuiObject.BackgroundTransparency|BackgroundTransparency`, `Class.TextLabel.TextTransparency|TextTransparency`, `Class.TextLabel.TextStrokeTransparency|TextStrokeTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton`</td>
|
||||
<td>`Class.GuiObject.BackgroundTransparency|BackgroundTransparency`, `Class.TextButton.TextTransparency|TextTransparency`, `Class.TextButton.TextStrokeTransparency|TextStrokeTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageLabel`</td>
|
||||
<td>`Class.GuiObject.BackgroundTransparency|BackgroundTransparency`, `Class.ImageLabel.ImageTransparency|ImageTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageButton`</td>
|
||||
<td>`Class.GuiObject.BackgroundTransparency|BackgroundTransparency`, `Class.ImageButton.ImageTransparency|ImageTransparency`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="info">
|
||||
As a modern alternative to `Class.TextLabel.TextStrokeTransparency` and `Class.TextButton.TextStrokeTransparency`, you can tween stroke transparency on a `Class.UIStroke` object as outlined in [Stroke](#stroke).
|
||||
</Alert>
|
||||
|
||||
```lua title="UI Tween - Image Transparency" highlight="8,11,13"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
local targetTransparency = 0.8
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {ImageTransparency = targetTransparency})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
```lua title="UI Tween - Canvas Group Transparency" highlight="8,11,13"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local canvasGroup = ScreenGui:WaitForChild("CanvasGroup")
|
||||
|
||||
local targetTransparency = 0.8
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(canvasGroup, tweenInfo, {GroupTransparency = targetTransparency})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
### Color
|
||||
|
||||
Multiple properties control UI color, depending on the object type. You can tween each of these properties individually or combined through a [multi-property tween](#multi-property-tweens). Alternatively, you can tween an object's overall color by placing it inside a `Class.CanvasGroup` and tweening the group's `Class.CanvasGroup.GroupColor3|GroupColor3`.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>UI Object</th>
|
||||
<th>Properties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Frame`</td>
|
||||
<td>`Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.BorderColor3|BorderColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextLabel`</td>
|
||||
<td>`Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.TextLabel.TextColor3|TextColor3`, `Class.TextLabel.TextStrokeColor3|TextStrokeColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton`</td>
|
||||
<td>`Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.TextButton.TextColor3|TextColor3`, `Class.TextButton.TextStrokeColor3|TextStrokeColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageLabel`</td>
|
||||
<td>`Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.ImageLabel.ImageColor3|ImageColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageButton`</td>
|
||||
<td>`Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.ImageButton.ImageColor3|ImageColor3`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Alert severity="info">
|
||||
As a modern alternative to `Class.GuiObject.BorderColor3`, `Class.TextLabel.TextStrokeColor3`, and `Class.TextButton.TextStrokeColor3`, you can tween stroke color on a `Class.UIStroke` object as outlined in [Stroke](#stroke).
|
||||
</Alert>
|
||||
|
||||
```lua title="UI Tween - Image Color" highlight="8,11,13"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
local targetColor = Color3.fromRGB(255, 0, 0)
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {ImageColor3 = targetColor})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
```lua title="UI Tween - Canvas Group Color" highlight="8,11,13"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local canvasGroup = ScreenGui:WaitForChild("CanvasGroup")
|
||||
|
||||
local targetColor = Color3.fromRGB(255, 0, 0)
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(canvasGroup, tweenInfo, {GroupColor3 = targetColor})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
### Stroke
|
||||
|
||||
Multiple properties control UI borders, depending on the object type.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>UI object</th>
|
||||
<th>Properties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Frame`</td>
|
||||
<td>`Class.GuiObject.BorderSizePixel|BorderSizePixel`, `Class.GuiObject.BorderColor3|BorderColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextLabel`</td>
|
||||
<td>`Class.GuiObject.BorderSizePixel|BorderSizePixel`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.TextLabel.TextStrokeColor3|TextStrokeColor3`, `Class.TextLabel.TextStrokeTransparency|TextStrokeTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton`</td>
|
||||
<td>`Class.GuiObject.BorderSizePixel|BorderSizePixel`, `Class.GuiObject.BorderColor3|BorderColor3`, `Class.TextButton.TextStrokeColor3|TextStrokeColor3`, `Class.TextButton.TextStrokeTransparency|TextStrokeTransparency`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageLabel`</td>
|
||||
<td>`Class.GuiObject.BorderSizePixel|BorderSizePixel`, `Class.GuiObject.BorderColor3|BorderColor3`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ImageButton`</td>
|
||||
<td>`Class.GuiObject.BorderSizePixel|BorderSizePixel`, `Class.GuiObject.BorderColor3|BorderColor3`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Alternatively, you can apply a `Class.UIStroke` child and tween its thickness, color, and/or transparency.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>UI Object</th>
|
||||
<th>Properties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.UIStroke`</td>
|
||||
<td>`Class.UIStroke.Color|Color`, `Class.UIStroke.Thickness|Thickness`, `Class.UIStroke.Transparency|Transparency`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```lua title="UI Tween - UIStroke Color & Thickness" highlight="8-11,13,14,17,19"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("TextLabel")
|
||||
|
||||
local stroke = Instance.new("UIStroke")
|
||||
stroke.Color = Color3.fromRGB(255, 255, 255)
|
||||
stroke.Thickness = 5
|
||||
stroke.Parent = object
|
||||
|
||||
local targetColor = Color3.fromRGB(255, 0, 0)
|
||||
local targetThickness = 10
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(stroke, tweenInfo, {Color = targetColor, Thickness = targetThickness})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
## Multi-property tweens
|
||||
|
||||
You can combine any of the [single-property tweens](#single-property-tweens) into more complex tweens by passing multiple target properties to `Class.TweenService:Create()`, for example **position + rotation** or **size + transparency**.
|
||||
|
||||
```lua title="UI Tween - Position & Rotation" highlight="8,10,11,14,16"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local targetPosition = UDim2.new(0.5, 0, 0.5, 0)
|
||||
local targetRotation = 45
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Position = targetPosition, Rotation = targetRotation})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
```lua title="UI Tween - Size & Transparency" highlight="13,14,17,19"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
|
||||
aspectRatioConstraint.Parent = object
|
||||
|
||||
local targetSize = UDim2.new(0.4, 0, 0.4, 0)
|
||||
local targetTransparency = 0.8
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Size = targetSize, ImageTransparency = targetTransparency})
|
||||
|
||||
tween:Play()
|
||||
```
|
||||
|
||||
## Tween sequences
|
||||
|
||||
You can chain UI animations to occur one after another by playing subsequent tweens upon the previous tween's `Class.TweenBase.Completed|Completed` event. For example, the following script moves an object to the center of the screen, then rotates it by 45°.
|
||||
|
||||
```lua title="UI Tween Sequence" highlight="10,11,14,15,18,21-23"
|
||||
local TweenService = game:GetService("TweenService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local PlayerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local ScreenGui = PlayerGui:WaitForChild("ScreenGui")
|
||||
local object = ScreenGui:WaitForChild("ImageLabel")
|
||||
|
||||
object.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
|
||||
local targetPosition = UDim2.new(0.5, 0, 0.5, 0)
|
||||
local targetRotation = 45
|
||||
|
||||
local tweenInfo = TweenInfo.new(2)
|
||||
local positionTween = TweenService:Create(object, tweenInfo, {Position = targetPosition})
|
||||
local rotationTween = TweenService:Create(object, tweenInfo, {Rotation = targetRotation})
|
||||
|
||||
-- Initially play position tween
|
||||
positionTween:Play()
|
||||
|
||||
-- Play rotation tween upon completion of position tween
|
||||
positionTween.Completed:Connect(function()
|
||||
rotationTween:Play()
|
||||
end)
|
||||
```
|
||||
|
||||
## Easing options
|
||||
|
||||
Using the easing options of `Datatype.TweenInfo`, you can control the easing **style** and **direction** of UI animations.
|
||||
|
||||
### Style
|
||||
|
||||
`Enum.EasingStyle` sets the rate of interpolation from start to end. By default, easing style is set to `Enum.EasingStyle.Quad`.
|
||||
|
||||
<table size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Style</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Linear**</td>
|
||||
<td>Moves at a constant speed.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Sine**</td>
|
||||
<td>Speed is determined by a sine wave for a gentle easing motion.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Quad**</td>
|
||||
<td>Similar to **Sine** but with a slightly sharper curve based on quadratic interpolation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Cubic**</td>
|
||||
<td>Similar to **Quad** but with a slightly sharper curve based on cubic interpolation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Quart**</td>
|
||||
<td>Similar to **Cubic** but with an even sharper curve based on quartic interpolation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Quint**</td>
|
||||
<td>Similar to **Quart** but with an even sharper curve based on quintic interpolation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Exponential**</td>
|
||||
<td>The sharpest curve based on exponential interpolation.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Circular**</td>
|
||||
<td>Follows a circular arc, such that acceleration is more sudden and deceleration more gradual versus **Quint** or **Exponential**.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Back**</td>
|
||||
<td>Slightly overshoots the target, then backs into place.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Bounce**</td>
|
||||
<td>Bounces backwards multiple times after reaching the target, before eventually settling.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Elastic**</td>
|
||||
<td>Moves as if attached to a rubber band, overshooting the target several times.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="EasingDirection = In">
|
||||
<img src="../assets/engine-api/enums/EasingStyle/Easing-Styles-In.png" width="800" alt="Graphs of EasingStyle variations with an 'In' EasingDirection." />
|
||||
</TabItem>
|
||||
<TabItem label="EasingDirection = Out">
|
||||
<img src="../assets/engine-api/enums/EasingStyle/Easing-Styles-Out.png" width="800" alt="Graphs of EasingStyle variations with an 'Out' EasingDirection." />
|
||||
</TabItem>
|
||||
<TabItem label="EasingDirection = InOut">
|
||||
<img src="../assets/engine-api/enums/EasingStyle/Easing-Styles-InOut.png" width="800" alt="Graphs of EasingStyle variations with an 'InOut' EasingDirection." />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```lua title="Easing Style - Cubic" highlight="1"
|
||||
local tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Cubic)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Rotation = 45})
|
||||
```
|
||||
|
||||
### Direction
|
||||
|
||||
`Enum.EasingDirection` defines how the [easing style](#style) interpolation applies to an object, with a default of **Out**. Note that a tween with **Linear** easing style is not affected, as linear interpolation is constant from start to end.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Direction</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**In**</td>
|
||||
<td>The easing style applies in a forward direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Out**</td>
|
||||
<td>The easing style applies in a reverse direction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**InOut**</td>
|
||||
<td>The easing style applies forward for the first half and in reverse for the second half.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
```lua title="Easing direction - InOut" highlight="1"
|
||||
local tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Cubic, Enum.EasingDirection.InOut)
|
||||
local tween = TweenService:Create(object, tweenInfo, {Rotation = 45})
|
||||
```
|
||||
|
||||
## Animate text
|
||||
|
||||
You can easily enhance text-based UI, such as cutscene banners, player instructions, and prompts, with animated effects.
|
||||
|
||||
### Typewriter effect
|
||||
|
||||
The "typewriter" effect is ideal for `Class.TextLabel|TextLabels` that tell a story, output NPC conversations, etc.
|
||||
|
||||
<video src="../assets/ui/ui-animation/Text-Typewriter.mp4" controls width="80%"></video>
|
||||
|
||||
1. Create a new `Class.ModuleScript` within **ReplicatedStorage**.
|
||||
1. Rename the new script **AnimateUI**.
|
||||
|
||||
<img src="../assets/ui/ui-animation/ModuleScript-AnimateUI.png" width="320" />
|
||||
|
||||
1. Paste the following code into the script:
|
||||
|
||||
```lua title="ModuleScript - AnimateUI"
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local SOURCE_LOCALE = "en"
|
||||
local translator = nil
|
||||
|
||||
local AnimateUI = {}
|
||||
|
||||
function AnimateUI.loadTranslator()
|
||||
pcall(function()
|
||||
translator = LocalizationService:GetTranslatorForPlayerAsync(Players.LocalPlayer)
|
||||
end)
|
||||
if not translator then
|
||||
pcall(function()
|
||||
translator = LocalizationService:GetTranslatorForLocaleAsync(SOURCE_LOCALE)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function AnimateUI.typeWrite(guiObject, text, delayBetweenChars)
|
||||
guiObject.Visible = true
|
||||
guiObject.AutoLocalize = false
|
||||
local displayText = text
|
||||
|
||||
-- Translate text if possible
|
||||
if translator then
|
||||
displayText = translator:Translate(guiObject, text)
|
||||
end
|
||||
|
||||
-- Replace line break tags so grapheme loop will not miss those characters
|
||||
displayText = displayText:gsub("<br%s*/>", "\n")
|
||||
-- Remove RichText tags since char-by-char animation will break the tags
|
||||
displayText = displayText:gsub("<[^<>]->", "")
|
||||
|
||||
-- Set translated/modified text on parent
|
||||
guiObject.Text = displayText
|
||||
|
||||
local index = 0
|
||||
for first, last in utf8.graphemes(displayText) do
|
||||
index += 1
|
||||
guiObject.MaxVisibleGraphemes = index
|
||||
task.wait(delayBetweenChars)
|
||||
end
|
||||
end
|
||||
|
||||
return AnimateUI
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
If your experience's **source language** is not English (`"en"`), change the locale code for `SOURCE_LOCALE` to match the source language in [localization settings](../production/localization/index.md#localization-settings).
|
||||
</Alert>
|
||||
|
||||
1. Create a `Class.TextLabel` in a suitable location, such as within a `Class.ScreenGui` parented to `Class.StarterGui`.
|
||||
1. Insert a new `Class.LocalScript` as a direct child of the label and paste in the following code. Note that each message is output by calling `AnimateUI.typeWrite()` with parameters for the parent object, the string to output, and the delay between characters.
|
||||
|
||||
```lua title="LocalScript" highlight="3,5,10,11,15,16"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local AnimateUI = require(ReplicatedStorage:WaitForChild("AnimateUI"))
|
||||
|
||||
local label = script.Parent
|
||||
|
||||
-- Load translator if experience is localized
|
||||
--AnimateUI.loadTranslator()
|
||||
|
||||
local message1 = [[Beyond this door is the<br /><font size="46" color="rgb(255,50,25)">Great Zorgoth...</font> <font size="40">🗡</font>]]
|
||||
AnimateUI.typeWrite(label, message1, 0.05)
|
||||
|
||||
task.wait(1)
|
||||
|
||||
local message2 = [[...who rules this dungeon <font color="rgb(255,200,50)">unchallenged!</font> <font size="30">😈</font>]]
|
||||
AnimateUI.typeWrite(label, message2, 0.05)
|
||||
```
|
||||
334
.cursor/rules/roblox/en-us/ui/appearance-modifiers.md
Normal file
334
.cursor/rules/roblox/en-us/ui/appearance-modifiers.md
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
---
|
||||
title: UI appearance modifiers
|
||||
description: Explains how to use appearance modifiers to customize basic user interface objects.
|
||||
---
|
||||
|
||||
By utilizing **appearance modifiers**, you can further customize the appearance of your `Class.GuiObject|GuiObjects`.
|
||||
|
||||
- Apply a [gradient](#gradient) to the background of an object.
|
||||
- Apply a [stroke](#stroke) to text or a border.
|
||||
- Set [rounded corners](#corners) for an object.
|
||||
- Increase [padding](#padding) between the borders of an object.
|
||||
|
||||
## Gradient
|
||||
|
||||
The `Class.UIGradient` object applies a color and transparency gradient to its parent `Class.GuiObject`.
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Example.png" width="480" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/UIGradient.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
You can configure the gradient by:
|
||||
|
||||
- Setting its [colors](#color-sequence) through a `Datatype.ColorSequence` in the gradient's `Class.UIGradient.Color|Color` property.
|
||||
- Setting its [transparency](#transparency) through a `Datatype.NumberSequence` in the gradient's `Class.UIGradient.Transparency|Transparency` property.
|
||||
- Choosing the gradient's starting point (inside or outside the parent's bounds) through the `Class.UIGradient.Offset|Offset` property.
|
||||
- Choosing the gradient's angle through the `Class.UIGradient.Rotation|Rotation` property.
|
||||
|
||||
### Color sequence
|
||||
|
||||
To set a gradient's color sequence:
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, select the `Class.UIGradient`.
|
||||
1. In the [Properties](../studio/properties.md) window, click inside the `Class.UIGradient.Color|Color` property field, then click the **⋯** button to the right of the input box. A color sequence pop-up displays.
|
||||
|
||||
<img src="../assets/studio/properties/UIGradient-Open-ColorSequence-Window.png"
|
||||
width="320" />
|
||||
|
||||
Each triangle on the bottom axis of the color sequence is a **keypoint** that determines the color value at that point.
|
||||
|
||||
<img src="../assets/studio/general/ColorSequence-White.png" width="640" alt="Color sequence popup from white to white" />
|
||||
|
||||
1. Click a keypoint in the color sequence, then click the small square next to **Color** to open the **Colors** pop-up window.
|
||||
1. Select the desired color for the keypoint.
|
||||
|
||||
<img src="../assets/studio/general/ColorSequence-Red-White.png" width="640" alt="Color sequence popup from red to white" />
|
||||
|
||||
1. If needed, you can:
|
||||
|
||||
- Add another keypoint by clicking anywhere on the graph.
|
||||
- Drag an existing keypoint to a new position, or select a keypoint and enter a specific time value through the **Time** input.
|
||||
- Delete a keypoint by selecting it and clicking the **Delete** button.
|
||||
- Reset the sequence by clicking the **Reset** button.
|
||||
|
||||
### Transparency
|
||||
|
||||
To adjust a gradient's transparency across its range:
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, select the `Class.UIGradient`.
|
||||
1. In the [Properties](../studio/properties.md) window, click inside the `Class.UIGradient.Transparency|Transparency` property field, then click the **⋯** button to the right of the input box. A number sequence pop-up displays.
|
||||
|
||||
<img src="../assets/studio/properties/UIGradient-Open-NumberSequence-Window.png"
|
||||
width="320" />
|
||||
|
||||
Each square across the number sequence graph is a
|
||||
**keypoint** that determines the transparency value at that point.
|
||||
|
||||
<img src="../assets/studio/general/NumberSequence-0.5-0.5.png" width="746" alt="Number sequence popup from 0.5 to 0.5" />
|
||||
|
||||
1. Click and drag any keypoint around, or select a keypoint and enter a specific time/value combination through the **Time** and **Value** inputs.
|
||||
|
||||
<img src="../assets/studio/general/NumberSequence-0-1.png" width="746" alt="Number sequence popup from 0 to 1" />
|
||||
|
||||
1. If needed, you can:
|
||||
|
||||
- Add another keypoint by clicking anywhere on the graph.
|
||||
- Delete a keypoint by selecting it and clicking the **Delete** button.
|
||||
- Reset the sequence by clicking the **Reset** button.
|
||||
|
||||
### Offset and rotation
|
||||
|
||||
The `Class.UIGradient.Offset|Offset` and `Class.UIGradient.Rotation|Rotation` properties let you adjust the gradient's control points and its angle. As illustrated in the following diagrams, `Class.UIGradient.Offset|Offset` is based on a **percentage** of the parent's width or height, and both positive or negative values are valid.
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Offset-X-0.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Offset|Offset.X` = `0`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Offset-X-Pos-0.25.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Offset|Offset.X` = `0.25`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Offset-X-Neg-0.25.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Offset|Offset.X` = `-0.25`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Similarly, when you rotate the gradient, the control points also rotate:
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Rotation-0.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Rotation|Rotation` = `0`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Rotation-Pos-45.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Rotation|Rotation` = `45`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item container XSmall={12} Small={12} Medium={4} Large={4} XLarge={4}>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGradient-Rotation-Neg-90.png" width="376" />
|
||||
<figcaption>`Class.UIGradient.Rotation|Rotation` = `-90`</figcaption>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
## Stroke
|
||||
|
||||
The `Class.UIStroke` instance applies an outline to text or a border. Key features include:
|
||||
|
||||
- Adjustable [color](#color-or-gradient) and [thickness](#thickness) of the stroke outline.
|
||||
- Ability to set the stroke [transparency](#transparency-1) independently of the parent's transparency.
|
||||
- Customizable [position and/or offset](#border-positionoffset) relative to the parent's border.
|
||||
- Three [corner styles](#corner-style) (round, bevel, or miter).
|
||||
- Stroke [gradient](#color-or-gradient) support through the `Class.UIGradient` instance.
|
||||
|
||||
### Thickness
|
||||
|
||||
You can set the stroke width through the `Class.UIStroke.Thickness|Thickness` property which is measured in pixels (default) or scaled relative to the parent, depending on the modifier's `Class.UIStroke.StrokeSizingMode|StrokeSizingMode`. If stroke is on text and `Class.UIStroke.StrokeSizingMode|StrokeSizingMode` is set to `Enum.StrokeSizingMode.ScaledSize|ScaledSize`, thickness is relative to the font size.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Thickness-4.png" />
|
||||
<figcaption>`Class.UIStroke.Thickness` = `4`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Thickness-12.png" />
|
||||
<figcaption>`Class.UIStroke.Thickness` = `12`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="warning">
|
||||
Avoid [tweening](../ui/animation.md) the `Class.UIStroke.Thickness|Thickness` property of a `Class.UIStroke` instance applied to **text** objects. This renders and stores many glyph sizes each frame, potentially causing performance issues or text flickering.
|
||||
</Alert>
|
||||
|
||||
### Color or gradient
|
||||
|
||||
You can set the stroke color through the `Class.UIStroke.Color|Color` property, as well as insert a child `Class.UIGradient` instance to create gradient strokes.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Color-Solid.png" />
|
||||
<figcaption>`Class.UIStroke.Color` = `(0, 95, 225)`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Color-Gradient.png" />
|
||||
<figcaption>`Class.UIStroke` with `Class.UIGradient` child</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<Alert severity="info">
|
||||
Both the parent object and `Class.UIStroke` can have child `Class.UIGradient` instances, letting you set gradients on the **stroke** and **fill** independently.
|
||||
</Alert>
|
||||
|
||||
### Text outline or border
|
||||
|
||||
Depending on its parent type, `Class.UIStroke` operates as either a **text outline** or as a **border**. When you parent `Class.UIStroke` to a text object like `Class.TextLabel`, it applies to the text's outline; when you parent `Class.UIStroke` to other `Class.GuiObject|GuiObjects`, it applies to the border.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-As-Text-Outline.png" />
|
||||
<figcaption>`Class.TextLabel` with `Class.UIStroke` child</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-As-Border.png" />
|
||||
<figcaption>`Class.Frame` with `Class.UIStroke` and `Class.UICorner` children</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
When applied to a text object, you can override the default stroke behavior by the `Class.UIStroke.ApplyStrokeMode|ApplyStrokeMode` property, letting you apply the stroke to the object's bounds instead of the text itself. You can even control the text outline and border independently by parenting two `Class.UIStroke` instances to a text object, one set to **Contextual** and the other to **Border**.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-As-Text-Outline.png" />
|
||||
<figcaption>`Class.UIStroke.ApplyStrokeMode` = `Enum.ApplyStrokeMode.Contextual|Contextual`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Stroke-Mode-Border.png" />
|
||||
<figcaption>`Class.UIStroke.ApplyStrokeMode` = `Enum.ApplyStrokeMode.Border|Border`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Border position/offset
|
||||
|
||||
`Class.UIStroke.BorderStrokePosition|BorderStrokePosition` sets the stroke's position relative to its parent's border and `Class.UIStroke.BorderOffset|BorderOffset` lets you specify an additional offset to the stroke's position.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-BorderStrokePosition-Center.png" width="312" />
|
||||
<figcaption>`Class.UIStroke.BorderStrokePosition|BorderStrokePosition` = `Enum.BorderStrokePosition.Center|Center`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-BorderStrokePosition-Inner.png" width="312" />
|
||||
<figcaption>`Class.UIStroke.BorderStrokePosition|BorderStrokePosition` = `Enum.BorderStrokePosition.Inner|Inner`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-BorderStrokePosition-Outer.png" width="312" />
|
||||
<figcaption>`Class.UIStroke.BorderStrokePosition|BorderStrokePosition` = `Enum.BorderStrokePosition.Outer|Outer`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-BorderOffset-Out.png" width="312" />
|
||||
<figcaption>`Class.UIStroke.BorderOffset|BorderOffset` = `(0.15, 0)`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-BorderOffset-In.png" width="312" />
|
||||
<figcaption>`Class.UIStroke.BorderOffset|BorderOffset` = `(0, -16)`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Transparency
|
||||
|
||||
The `Class.UIStroke.Transparency|Transparency` property sets the stroke transparency independently of the parent object's `Class.GuiObject.BackgroundTransparency|BackgroundTransparency` or `Class.TextLabel.TextTransparency|TextTransparency`. This allows you to render text and borders that are "hollow" (consisting of only an outline).
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Transparency-A.png" />
|
||||
<figcaption>`Class.TextLabel.TextTransparency` = `0`<br />`Class.UIStroke.Transparency` = `0.5`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Transparency-B.png" />
|
||||
<figcaption>`Class.TextLabel.TextTransparency` = `1`<br />`Class.UIStroke.Transparency` = `0`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Corner style
|
||||
|
||||
The `Class.UIStroke.LineJoinMode|LineJoinMode` property lets you control how corners are interpreted. It accepts a value of either `Enum.LineJoinMode.Round|Round`, `Enum.LineJoinMode.Bevel|Bevel`, or `Enum.LineJoinMode.Miter|Miter`.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-LineJoinMode-Round.png" />
|
||||
<figcaption>`Class.UIStroke.LineJoinMode|LineJoinMode` = `Enum.LineJoinMode.Round|Round`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-LineJoinMode-Bevel.png" />
|
||||
<figcaption>`Class.UIStroke.LineJoinMode|LineJoinMode` = `Enum.LineJoinMode.Bevel|Bevel`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-LineJoinMode-Miter.png" />
|
||||
<figcaption>`Class.UIStroke.LineJoinMode|LineJoinMode` = `Enum.LineJoinMode.Miter|Miter`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
### Layering
|
||||
|
||||
To layer multiple sibling `Class.UIStroke` instances on a `Class.GuiObject` from front to back, you can utilize the `Class.UIStroke.ZIndex|ZIndex` property. Those with a lower `Class.UIStroke.ZIndex|ZIndex` render under (behind) those with a higher `Class.UIStroke.ZIndex|ZIndex`.
|
||||
|
||||
Note that the rendering order for `Class.UIStroke` instances with the same `Class.UIStroke.ZIndex|ZIndex` is undefined. Do not apply multiple `Class.UIStroke` instances with the same index if their rendering order matters.
|
||||
|
||||
## Corners
|
||||
|
||||
The `Class.UICorner` instance applies deformation to all four corners of its parent `Class.GuiObject`. You can control the applied radius through the `Class.UICorner.CornerRadius|CornerRadius` property using either `Datatype.UDim.Scale|Scale` or `Datatype.UDim.Offset|Offset`.
|
||||
|
||||
<img src="../assets/studio/properties/UICorner-CornerRadius.png"
|
||||
width="320" />
|
||||
|
||||
`Datatype.UDim.Scale|Scale` rounds the corners to a **percentage** based on the total length of the **shortest** edge of the parent, meaning that a scale of `0.5` or higher deforms the parent into a "pill" shape, regardless of its width or height. `Datatype.UDim.Offset|Offset` rounds the corners to a specific number of **pixels**, regardless of the width/height of the parent.
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UICorner-Scale-0.25.png" width="364" />
|
||||
<center><figcaption>`Datatype.UDim.Scale|Scale` = `0.25` · `Datatype.UDim.Offset|Offset` = `0`</figcaption></center>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UICorner-Scale-0.5.png" width="364" />
|
||||
<center><figcaption>`Datatype.UDim.Scale|Scale` = `0.5` · `Datatype.UDim.Offset|Offset` = `0`</figcaption></center>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UICorner-Offset-32.png" width="364" />
|
||||
<center><figcaption>`Datatype.UDim.Scale|Scale` = `0` · `Datatype.UDim.Offset|Offset` = `32`</figcaption></center>
|
||||
</figure>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UICorner-Offset-64.png" width="364" />
|
||||
<center><figcaption>`Datatype.UDim.Scale|Scale` = `0` · `Datatype.UDim.Offset|Offset` = `64`</figcaption></center>
|
||||
</figure>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
## Padding
|
||||
|
||||
A `Class.UIPadding` object applies top, bottom, left, and/or right padding to the contents of the parent `Class.GuiObject`. For example, you can move the text inside a text label downward or upward by applying an offset to the modifier's `Class.UIPadding.PaddingBottom|PaddingBottom` property.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIPadding-PaddingBottom-Default.png" width="300" />
|
||||
<figcaption>`Class.TextLabel` without `Class.UIPadding`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIPadding-PaddingBottom-Offset-Negative.png" width="300" />
|
||||
<figcaption>`Class.UIPadding.PaddingBottom` = `(0, -20)`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIPadding-PaddingBottom-Offset-Positive.png" width="300" />
|
||||
<figcaption>`Class.UIPadding.PaddingBottom` = `(0, 30)`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
108
.cursor/rules/roblox/en-us/ui/buttons.md
Normal file
108
.cursor/rules/roblox/en-us/ui/buttons.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
title: Text & image buttons
|
||||
description: Buttons allow users to prompt an action.
|
||||
---
|
||||
|
||||
**Buttons** are `Class.GuiObject|GuiObjects` that allow users to perform an action. You can customize buttons to provide context and feedback, such as changing the visual appearance or [scripting](#script-buttons) audible feedback when a user clicks a button.
|
||||
|
||||
There are two types of buttons which you can place [on‑screen](../ui/on-screen-containers.md) or [in‑experience](../ui/in-experience-containers.md):
|
||||
|
||||
- A `Class.TextButton` is a rectangle with text that triggers the `Class.GuiButton.Activated|Activated` event on click/tap.
|
||||
|
||||
- An `Class.ImageButton` is a rectangle with an image that triggers the `Class.GuiButton.Activated|Activated` event on click/tap. It features additional states for swapping the image on user hover or press.
|
||||
|
||||
## Create buttons on the screen
|
||||
|
||||
Buttons on a screen are useful to quickly guide users to various menus or pages.
|
||||
|
||||
To add a button to the screen:
|
||||
|
||||
1. In the **Explorer** window, select **StarterGui** and add a **ScreenGui**.
|
||||
|
||||
1. Hover over **StarterGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **ScreenGui**.
|
||||
|
||||
2. Select the new **ScreenGui** and add a button.
|
||||
|
||||
1. Hover over **ScreenGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert either a **TextButton** or **ImageButton**.
|
||||
|
||||
<img src="../assets/ui/button-text-input/ImageButton-Screen-New.jpg" width="800" />
|
||||
|
||||
## Create buttons on part faces
|
||||
|
||||
Buttons on a part are useful for allowing users to interact with parts. For
|
||||
example, you can let users step on a button to complete an action.
|
||||
|
||||
To add a button to the face of a part:
|
||||
|
||||
1. In the **Explorer** window, select the **part** and add a **SurfaceGui**.
|
||||
|
||||
1. Hover over the **part** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **SurfaceGui**.
|
||||
|
||||
2. Select the new **SurfaceGui** and add any type of button or input.
|
||||
|
||||
1. Hover over **SurfaceGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert either a **TextButton** or
|
||||
**ImageButton**.
|
||||
|
||||
<img src="../assets/ui/button-text-input/ImageButton-SurfaceGui-New.jpg"
|
||||
width="800" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the button, try [choosing a different face](../parts/textures-decals.md#choose-a-face) in the <b>Face</b> property of the <b>SurfaceGui</b>.
|
||||
</Alert>
|
||||
|
||||
## Change the appearance of an ImageButton
|
||||
|
||||
Changing the appearance of an `Class.ImageButton` when a user is interacting with it provides useful visual feedback. For example, when an `Class.ImageButton` changes visual appearance when a user hovers over it, it lets the user know that it isn't disabled and that they have the option to click it if they want to perform that `Class.ImageButton` action.
|
||||
|
||||
An `Class.ImageButton` has three properties to change its visual appearance:
|
||||
|
||||
- `Class.ImageButton.Image|Image` - The image that displays when a user is not interacting with the `Class.ImageButton`.
|
||||
|
||||
- `Class.ImageButton.HoverImage|HoverImage` - The image that displays when a user is hovering their cursor over the `Class.ImageButton`.
|
||||
|
||||
- `Class.ImageButton.PressedImage|PressedImage` - The image that displays when a user is clicking the `Class.ImageButton`.
|
||||
|
||||
<GridContainer numColumns="3">
|
||||
<figure>
|
||||
<img src="../assets/ui/button-text-input/ImageButton-State-Normal.png" />
|
||||
<figcaption>Normal</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/button-text-input/ImageButton-State-Hover.png" />
|
||||
<figcaption>Hover</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/button-text-input/ImageButton-State-Pressed.png" />
|
||||
<figcaption>Pressed</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
To change the appearance of an `Class.ImageButton` with user input:
|
||||
|
||||
1. Add an **ImageButton** to a [screen](#create-buttons-on-the-screen) or a [surface](#create-buttons-on-the-screen).
|
||||
|
||||
2. In the **Explorer** window, click the **ImageButton** object.
|
||||
|
||||
3. In the **Properties** window, assign three different respective asset IDs for the **Image**, **HoverImage**, and **PressedImage** properties.
|
||||
|
||||
## Script buttons
|
||||
|
||||
You can script an action when a user presses a button by connecting the button to a `Class.GuiButton.Activated` event. For example, when you parent the following `Class.LocalScript` to a button, the button changes to a random color every time a user clicks it.
|
||||
|
||||
```lua
|
||||
local button = script.Parent
|
||||
local RNG = Random.new()
|
||||
local function onButtonActivated()
|
||||
-- randomize the button color
|
||||
button.BackgroundColor3 = Color3.fromHSV(RNG:NextNumber(), 1, 1)
|
||||
end
|
||||
button.Activated:Connect(onButtonActivated)
|
||||
```
|
||||
26
.cursor/rules/roblox/en-us/ui/frames.md
Normal file
26
.cursor/rules/roblox/en-us/ui/frames.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: Frames
|
||||
description: Frames are basic containers for UI objects.
|
||||
---
|
||||
|
||||
Basic `Class.Frame|Frames` act as containers for other `Class.GuiObject|GuiObjects` such as [labels](../ui/labels.md) and [buttons](../ui/buttons.md). You can apply frames to display on a user's [screen](../ui/on-screen-containers.md) or on a [surface](../ui/in-experience-containers.md) within the experience.
|
||||
|
||||
<img src="../assets/ui/ui-objects/Frame-Example.jpg" width="840" alt="Example Frame on the screen containing a TextLabel, TextBox, and ImageButton." />
|
||||
|
||||
Frames are ideal containers for responsive layouts such as [list and flex layouts](../ui/list-flex-layouts.md), allowing you to change the size of the frame and dynamically adjust how layout items fit within it. `Class.Frame|Frames` are also core `Class.GuiObject|GuiObjects`, so you can customize properties such as `Class.GuiObject.BackgroundColor3|BackgroundColor3`, `Class.GuiObject.Transparency|Transparency`, apply a [background gradient](../ui/appearance-modifiers.md#gradient) or [border](../ui/appearance-modifiers.md#stroke), and more.
|
||||
|
||||
Aside from their common use as containers, you can also use `Class.Frame|Frames` for UI design. For example, as a visual separator between other UI elements, you can scale a frame to be thin and long until it becomes a line.
|
||||
|
||||
<Alert severity="success">
|
||||
See also [scrolling frames](../ui/scrolling-frames.md), [viewport frames](../ui/viewport-frames.md), and [video frames](../ui/video-frames.md) as specialty frame types with unique built‑in capabilities.
|
||||
</Alert>
|
||||
|
||||
## Clipping
|
||||
|
||||
By default, `Class.Frame` containers **clip** their content (child `Class.GuiObject|GuiObjects`) through the `Class.Frame.ClipsDescendants|ClipsDescendants` boolean. If you want children to appear outside of a frame's bounds, simply set `Class.Frame.ClipsDescendants|ClipsDescendants` to `false`.
|
||||
|
||||
Importantly, note that `Class.Frame.ClipsDescendants|ClipsDescendants` does **not** apply if the frame or any of its ancestors have a non‑zero `Class.Frame.Rotation|Rotation`; in such cases, descendants will render outside of the frame's bounds.
|
||||
|
||||
## Automatic sizing
|
||||
|
||||
Frames can be set to [automatically size](../ui/size-modifiers.md#automatic-sizing) depending on their content. To achieve this, set the frame's `Class.Frame.Size|Size` to the **minimum** width and/or height, then set its `Class.Frame.AutomaticSize|AutomaticSize` property to either `Enum.AutomaticSize|X`, `Enum.AutomaticSize|Y`, or `Enum.AutomaticSize|XY` depending on which axes automatic sizing should apply to. Once set, the frame will automatically resize based on the size of `Class.GuiObject|GuiObjects` and layouts within it.
|
||||
89
.cursor/rules/roblox/en-us/ui/grid-table-layouts.md
Normal file
89
.cursor/rules/roblox/en-us/ui/grid-table-layouts.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: Grid and table layouts
|
||||
description: How to use grid and table layouts for highly structured user interfaces.
|
||||
---
|
||||
|
||||
In comparison to `Class.UIListLayout`, `Class.UIGridLayout` and `Class.UITableLayout` allow for more structured and organized layouts. These are most appropriate for interfaces like a shop inventory where each item can be presented in a grid of equally‑sized tiles, or items can be sorted into related rows/columns.
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIGridLayout-Example.jpg" />
|
||||
<figcaption>`Class.UIGridLayout`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UITableLayout-Example.jpg" />
|
||||
<figcaption>`Class.UITableLayout`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Grid layout
|
||||
|
||||
`Class.UIGridLayout` positions sibling `Class.GuiObject|GuiObjects` in a grid of uniform cells of the same size within their parent container. Cells are added by row or column based on the layout's `Class.UIGridLayout.FillDirection|FillDirection` until the next cell doesn't fit, then a new row or column begins. For further control, you can use the `Class.UIGridLayout.FillDirectionMaxCells|FillDirectionMaxCells` property to set the maximum number of cells per row or column.
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<img src="../assets/ui/ui-objects/UIGridLayout-Example.jpg" width="500" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/UIGridLayout.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
By default, `Class.UIGridLayout` positions sibling `Class.GuiObject|GuiObjects` in order of their `Class.GuiObject.LayoutOrder|LayoutOrder` where lower values go before higher values, and equal values sort depending on the order in which you added them. If you change the layout's `Class.UIGridStyleLayout.SortOrder|SortOrder` to `Enum.SortOrder.Name`, siblings sort in alphabetical order.
|
||||
|
||||
<Alert severity="warning">
|
||||
Once you insert a `Class.UIGridLayout`, it either overrides or influences the `Class.GuiObject.Position|Position` and/or `Class.GuiObject.Size|Size` of all sibling UI objects, so changes to those properties within the [Properties](../studio/properties.md) window or within a script will not have the normal effect.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
`Class.UIGridLayout` respects any [constraints](../ui/size-modifiers.md) you place on objects within the grid. For example, if an object has a `Class.UISizeConstraint` with a `Class.UISizeConstraint.MinSize|MinSize` property that is higher than the grid's `Class.UIGridLayout.CellSize|CellSize` property, the object will span multiple cells.
|
||||
</Alert>
|
||||
|
||||
## Table layout
|
||||
|
||||
`Class.UITableLayout` positions sibling `Class.GuiObject|GuiObjects` and their children into table format. The default `Class.UITableLayout.FillDirection|FillDirection` of `Enum.FillDirection|Vertical` means that siblings are first positioned into rows, and children of those siblings are positioned horizontally to form columns, such that each cell within a row has the same height and each cell within a column has the same width.
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<img src="../assets/ui/ui-objects/UITableLayout-Example.jpg" width="500" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/UITableLayout.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
This pattern mimics the standard HTML row/column structure:
|
||||
|
||||
```html
|
||||
<table>
|
||||
<tbody>
|
||||
<tr> <!-- Row1 -->
|
||||
<td>Label 1</td> <!-- TextLabel1 -->
|
||||
<td>Label 2</td> <!-- TextLabel2 -->
|
||||
<td>Label 3</td> <!-- TextLabel3 -->
|
||||
</tr>
|
||||
<tr> <!-- Row2 -->
|
||||
<td>Label 4</td> <!-- TextLabel4 -->
|
||||
<td>Label 5</td> <!-- TextLabel5 -->
|
||||
<td>Label 6</td> <!-- TextLabel6 -->
|
||||
</tr>
|
||||
<tr> <!-- Row3 -->
|
||||
<td>Label 7</td> <!-- TextLabel7 -->
|
||||
<td>Label 8</td> <!-- TextLabel8 -->
|
||||
<td>Label 9</td> <!-- TextLabel9 -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
|
||||
Unless you enable the layout's `Class.UITableLayout.FillEmptySpaceColumns|FillEmptySpaceColumns` or `Class.UITableLayout.FillEmptySpaceRows|FillEmptySpaceRows` properties, the size of the sibling `Class.GuiObject|GuiObjects` determines the dimensions of the cells. For example, a `Class.TextLabel` of size `Datatype.UDim2.new(0.25, 0, 0.1, 0)` will create a table cell of 25% width and 10% height inside the parent container.
|
||||
|
||||
<Alert severity="warning">
|
||||
Once you insert a `Class.UITableLayout`, it either overrides or influences the `Class.GuiObject.Position|Position` and/or `Class.GuiObject.Size|Size` of all sibling UI objects, so changes to those properties within the [Properties](../studio/properties.md) window or within a script will not have the normal effect.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
`Class.UITableLayout` respects and is responsive to any `Class.UISizeConstraint` you place on objects within the table. For example, if an object has a `Class.UISizeConstraint` with a `Class.UISizeConstraint.MinSize|MinSize` property within a header cell, the rest of the cells within the column will be the same size as the header cell.
|
||||
|
||||
Note that if the `Class.UISizeConstraint.MaxSize|MaxSize` property restricts a cell's size from filling the allotted space of a row or column that is wider than the cell, the object aligns to the top‑left of the cell.
|
||||
</Alert>
|
||||
152
.cursor/rules/roblox/en-us/ui/in-experience-containers.md
Normal file
152
.cursor/rules/roblox/en-us/ui/in-experience-containers.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
title: In-experience UI containers
|
||||
description: In-experience UI containers hold SurfaceGuis, BillboardGuis, and GuiObjects that you want to display in the 3D space.
|
||||
---
|
||||
|
||||
In-experience UI containers hold `Class.GuiObject|GuiObjects` that you want to display within your experience's 3D world.
|
||||
|
||||
- A `Class.SurfaceGui` allows for the rendering of UI objects onto a part's surface in the 3D world while also allowing for basic user interaction to occur.
|
||||
- A `Class.BillboardGui` is a container for UI objects to appear in the 3D space but always face the camera.
|
||||
|
||||
<video src="../assets/ui/in-experience/Showcase.mp4" controls width="100%"></video>
|
||||
|
||||
## Surface UI
|
||||
|
||||
Similar to `Class.Decal|Decals` and `Class.Texture|Textures`, UI objects such as `Class.TextLabel|TextLabels` and `Class.ImageLabel|ImageLabels` parented to a `Class.SurfaceGui` face the same direction as the surface they're on, editable through the `Class.SurfaceGui.Face` property.
|
||||
|
||||
<img src="../assets/ui/in-experience/SurfaceGui-Diagram.jpg" width="800" alt="SurfaceGui on a 3D part in the place with an ImageLabel child to depict a screen console." />
|
||||
|
||||
To apply a `Class.SurfaceGui` to an in-experience `Class.BasePart`, simply parent it to that part and set the `Class.SurfaceGui.Face` property. Child UI objects then appear on that face of the parent part.
|
||||
|
||||
<Grid container spacing={2} alignItems="top">
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/Part-SurfaceGui-ImageLabel.png" width="320" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/properties/SurfaceGui-Face-Top.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Alternatively, you can place the `Class.SurfaceGui` inside a container like `Class.StarterGui` and then set its `Class.SurfaceGui.Adornee|Adornee` property to any `Class.BasePart`, as well as the target `Class.SurfaceGui.Face|Face`. Setting `Class.SurfaceGui.Adornee|Adornee` overrides direct parent association, allowing for more flexibility in placement since it can be set from a script during runtime.
|
||||
|
||||
<Grid container spacing={2} alignItems="top">
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/StarterGui-SurfaceGui.png" width="320" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/properties/SurfaceGui-Adornee-Face.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Alert severity="warning">
|
||||
Interactive UI elements like `Class.ImageButton|ImageButtons` and `Class.TextButton|TextButtons` inside a `Class.SurfaceGui` will only receive user input if they are parented to the player's `Class.PlayerGui`, typically via placing the `Class.SurfaceGui` inside `Class.StarterGui` as noted above. Additionally, the part's `Class.BasePart.CanQuery|CanQuery` property must be `true` for the interactive UI element to receive input.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="success">
|
||||
To assist in choosing the correct face for a `Class.SurfaceGui`, right-click the target part and select **Show Orientation Indicator**. This displays a blue circle with an **F** and a line attached to the object's `Enum.NormalId.Front|Front` face, and a green arrow that points in the direction of the object's `Enum.NormalId.Top|Top` face.
|
||||
</Alert>
|
||||
|
||||
### Size and position
|
||||
|
||||
The "canvas" of a `Class.SurfaceGui` occupies the entire `Class.SurfaceGui.Face|Face` of the parent or `Class.SurfaceGui.Adornee|Adornee` part. As a best practice, it's recommended that you use **scale** values for the [size](../ui/position-and-size.md#size) and [position](../ui/position-and-size.md#position) of child UI objects like `Class.ImageLabel|ImageLabels`. You can also apply a `Class.UIAspectRatioConstraint` to children of the `Class.SurfaceGui` to maintain their desired aspect ratio regardless of the face's size.
|
||||
|
||||
<video src="../assets/ui/in-experience/SurfaceGui-Sizing.mp4" controls width="90%"></video>
|
||||
|
||||
### Occlusion mode
|
||||
|
||||
The `Class.SurfaceGui.AlwaysOnTop|AlwaysOnTop` property determines whether the `Class.SurfaceGui` will render over top of 3D content or be occluded by it. When set to `false` (default), the `Class.SurfaceGui` renders like other 3D content and is occluded by other 3D objects. When set to `true`, it always renders over top of 3D content and it is not influenced by [brightness/light](#brightness-and-light-influence) in the 3D environment.
|
||||
|
||||
### Brightness and light influence
|
||||
|
||||
`Class.SurfaceGui.Brightness|Brightness` and `Class.SurfaceGui.LightInfluence|LightInfluence` work in conjunction to determine how environmental light affects the UI content of the `Class.SurfaceGui`.
|
||||
|
||||
`Class.SurfaceGui.Brightness|Brightness` determines the factor by which emitted light is scaled in a range of `0` to `1000`, letting you match the `Class.SurfaceGui` to its environment. For instance, a video billboard can be brightened inside a dark room by increasing `Class.SurfaceGui.Brightness|Brightness` to `10`.
|
||||
|
||||
`Class.SurfaceGui.LightInfluence|LightInfluence` controls how much the `Class.SurfaceGui` is influenced by lighting in the place, in a range from `0` to `1`. Setting this to `1` means that surrounding lighting has complete control over the appearance, while setting it to `0` means that the lighting has no effect.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Brightness = 0">
|
||||
<img src="../assets/ui/in-experience/SurfaceGui-Brightness-0.jpg" width="800" height="450" />
|
||||
</TabItem>
|
||||
<TabItem label="Brightness = 10">
|
||||
<img src="../assets/ui/in-experience/SurfaceGui-Brightness-10.jpg" width="800" height="450" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Alert severity="warning">
|
||||
Note that `Class.SurfaceGui.Brightness|Brightness` is inaccessible in Studio and has no effect when either `Class.SurfaceGui.LightInfluence|LightInfluence` is `1` or `Class.SurfaceGui.AlwaysOnTop|AlwaysOnTop` is `true`.
|
||||
</Alert>
|
||||
|
||||
### Distance visibility
|
||||
|
||||
`Class.SurfaceGui.MaxDistance|MaxDistance` controls how far from the camera the `Class.SurfaceGui` will be displayed before it stops rendering. A value of `0` means there is no limit and it will render infinitely far away. The default value of `1000` works fine for most cases.
|
||||
|
||||
For `Class.SurfaceGui|SurfaceGuis` that appear outdoors, it's recommended that `Class.SurfaceGui.MaxDistance|MaxDistance` is high enough to ensure that the container's UI is sufficiently small on the screen when it appears or disappears, minimizing the sudden pop‑in/out effect.
|
||||
|
||||
### Display order
|
||||
|
||||
If multiple `Class.SurfaceGui` containers exist on the same face, you can layer them by Z‑index through their `Class.SurfaceGui.ZOffset|ZOffset` property (changing this does not visually "lift" or "sink" the container from the surface).
|
||||
|
||||
## Billboard UI
|
||||
|
||||
The `Class.BillboardGui` container displays UI objects in the 3D space but, unlike `Class.SurfaceGui`, children of a `Class.BillboardGui` always face the camera. This container is useful for displaying front‑facing info above in‑experience objects such as health meters or names above player characters, markers to guide players to an object in the 3D world, and more.
|
||||
|
||||
<img src="../assets/ui/in-experience/BillboardGui-Diagram.jpg" width="800" alt="BillboardGui with a TextLabel describing the screen console it floats above." />
|
||||
|
||||
To link a `Class.BillboardGui` to an in-experience `Class.BasePart` or `Class.Attachment`, simply parent it to that part or attachment and, if desired, adjust its [size/position](#size-and-position-1).
|
||||
|
||||
<Grid container spacing={2} alignItems="top">
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/Part-BillboardGui-ImageLabel.png" width="320" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/studio/explorer/Part-Attachment-BillboardGui-ImageLabel.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
Alternatively, you can place the `Class.BillboardGui` inside a container like `Class.StarterGui` and then set its `Class.BillboardGui.Adornee|Adornee` property to any `Class.BasePart` or `Class.Attachment`. This method offers more flexibility in placement since `Class.BillboardGui.Adornee|Adornee` can be set from a script during runtime, for example to place a `Class.BillboardGui` over the head of player characters during runtime.
|
||||
|
||||
<Alert severity="warning">
|
||||
Interactive UI elements like `Class.ImageButton|ImageButtons` and `Class.TextButton|TextButtons` inside a `Class.BillboardGui` will only receive user input if they are parented to the player's `Class.PlayerGui`, typically via placing the `Class.BillboardGui` inside `Class.StarterGui` as noted above.
|
||||
</Alert>
|
||||
|
||||
### Size and position
|
||||
|
||||
For billboard sizing, the [scale](../ui/position-and-size.md#size) components of the `Class.BillboardGui.Size|Size` property set the billboard's stud size in 3D space. For example, a setting of <Typography noWrap>`{10, 0},{2, 0}`</Typography> <Typography noWrap>(`Datatype.UDim2.fromScale(10, 2)`)</Typography> forms a billboard with a 10:2 aspect ratio that scales larger or smaller depending on its distance from the camera.
|
||||
|
||||
For positioning, the `Class.BillboardGui.StudsOffset|StudsOffset` property shifts the billboard canvas on the **X** axis (left/right), **Y** axis (up/down), and **Z** axis (forward/back) relative to the camera.
|
||||
|
||||
<video src="../assets/ui/in-experience/BillboardGui-Sizing-Positioning.mp4" controls width="90%"></video>
|
||||
|
||||
<Alert severity="success">
|
||||
When creating a size-scaled `Class.BillboardGui` that contains a `Class.TextLabel`, it's useful to enable the label's `Class.TextLabel.TextScaled|TextScaled` property so that its text scales along with the billboard canvas as the camera distance changes.
|
||||
</Alert>
|
||||
|
||||
### Occlusion mode
|
||||
|
||||
The `Class.BillboardGui.AlwaysOnTop|AlwaysOnTop` property determines whether the `Class.BillboardGui` will render over top of 3D content or be occluded by it. When set to `false` (default), the `Class.BillboardGui` renders like other 3D content and is occluded by other 3D objects. When set to `true`, it always renders over top of 3D content and it is not influenced by [brightness/light](#brightness-and-light-influence-1) in the 3D environment.
|
||||
|
||||
### Brightness and light influence
|
||||
|
||||
`Class.BillboardGui.Brightness|Brightness` and `Class.BillboardGui.LightInfluence|LightInfluence` work in conjunction to determine how environmental light affects the UI content of the `Class.BillboardGui`.
|
||||
|
||||
`Class.BillboardGui.Brightness|Brightness` determines the factor by which emitted light is scaled in a range of `0` to `1000`, letting you match the `Class.BillboardGui` to its environment. For instance, a video billboard can be brightened inside a dark room by increasing `Class.BillboardGui.Brightness|Brightness` to `10`.
|
||||
|
||||
`Class.BillboardGui.LightInfluence|LightInfluence` controls how much the `Class.BillboardGui` is influenced by lighting in the place, in a range from `0` to `1`. Setting this to `1` means that surrounding lighting has complete control over the appearance, while setting it to `0` means that the lighting has no effect.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="LightInfluence = 1">
|
||||
<img src="../assets/ui/in-experience/BillboardGui-LightInfluence-1.jpg" width="800" height="450" />
|
||||
</TabItem>
|
||||
<TabItem label="LightInfluence = 0">
|
||||
<img src="../assets/ui/in-experience/BillboardGui-LightInfluence-0.jpg" width="800" height="450" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
<Alert severity="warning">
|
||||
Note that `Class.BillboardGui.Brightness|Brightness` is inaccessible in Studio and has no effect when either `Class.BillboardGui.LightInfluence|LightInfluence` is `1` or `Class.BillboardGui.AlwaysOnTop|AlwaysOnTop` is `true`.
|
||||
</Alert>
|
||||
|
||||
### Distance visibility
|
||||
|
||||
`Class.BillboardGui.MaxDistance|MaxDistance` controls how far from the camera the `Class.BillboardGui` will be displayed before it stops rendering. A value of `0` or `inf` (default) means there is no limit and it will render infinitely far away.
|
||||
101
.cursor/rules/roblox/en-us/ui/index.md
Normal file
101
.cursor/rules/roblox/en-us/ui/index.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: User interface
|
||||
description: Explore the wide variety of user interface elements that players can interact with.
|
||||
---
|
||||
|
||||
You can quickly create high-quality graphical user interfaces with minimal scripting requirements using built-in [UI objects](#ui-objects). Depending on where you create it, UI renders either [on-screen](#on-screen-ui) or [within an experience's 3D world](#in-experience-ui).
|
||||
|
||||
## On-screen UI
|
||||
|
||||
[On-screen containers](../ui/on-screen-containers.md) hold UI objects that you want to display on a player's screen, including [frames](../ui/frames.md), [labels](../ui/labels.md), [buttons](../ui/buttons.md), and more. All on-screen UI objects and code are stored and changed on the client.
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/ScreenGui-Example.jpg" width="840" alt="Example ScreenGui with various GuiObject children, including a Frame, TextLabel, TextBox, and ImageButton." />
|
||||
</figure>
|
||||
|
||||
## In-experience UI
|
||||
|
||||
[In-experience containers](../ui/in-experience-containers.md) such as `Class.SurfaceGui|SurfaceGuis` and `Class.BillboardGui|BillboardGuis` hold UI objects that you want to display within your experience's 3D world.
|
||||
|
||||
<video src="../assets/ui/in-experience/Showcase.mp4" controls width="100%"></video>
|
||||
|
||||
## UI objects
|
||||
|
||||
Most UI elements are `Class.GuiObject|GuiObjects`, 2D graphical user interface objects that you can parent to containers. The four most common are [frames](../ui/frames.md), [labels](../ui/labels.md), [buttons](../ui/buttons.md), and [text input](../ui/text-input.md) objects.
|
||||
|
||||
<img src="../assets/ui/ui-objects/ScreenGui-Example.jpg" width="840" />
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Object</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>[Frame](../ui/frames.md)</td>
|
||||
<td>`Class.Frame|Frames` act as containers for other UI objects. When you manipulate frames, you also manipulate the objects they contain.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Label](../ui/labels.md)</td>
|
||||
<td>`Class.TextLabel` and `Class.ImageLabel` objects allow you to display customizable text and images.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Button](../ui/buttons.md)</td>
|
||||
<td>`Class.TextButton` and `Class.ImageButton` objects allow users to prompt an action.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Text Input](../ui/text-input.md)</td>
|
||||
<td>`Class.TextBox` objects allow users to input text.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Using the `Class.GuiObject.Position|Position`, `Class.GuiObject.Size|Size`, `Class.GuiObject.AnchorPoint|AnchorPoint`, and `Class.GuiObject.ZIndex|ZIndex` properties, you have complete control on how to [position](../ui/position-and-size.md#position), [size](../ui/position-and-size.md#size), and [layer](../ui/position-and-size.md#zindex) `Class.GuiObject|GuiObjects`. You can also use [tweening](../ui/animation.md) to transition a `Class.GuiObject` smoothly from one state to another and provide dynamic visual feedback.
|
||||
|
||||
## Layout and design
|
||||
|
||||
Beyond basic properties for adjusting position and size, Roblox also provides layout structures like [list/flex](../ui/list-flex-layouts.md) and [grid](../ui/grid-table-layouts.md), as well as [size modifiers](../ui/size-modifiers.md) and [appearance modifiers](../ui/appearance-modifiers.md).
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIListLayout-Example.png" width="840" />
|
||||
<figcaption>`Class.UIListLayout` for horizontal or vertical item sequences</figcaption>
|
||||
</figure>
|
||||
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Color-Solid.png" />
|
||||
<figcaption>`Class.UIStroke` on `Class.TextLabel`</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIStroke-Color-Gradient.png" />
|
||||
<figcaption>`Class.UIStroke` with `Class.UIGradient`</figcaption>
|
||||
</figure>
|
||||
</GridContainer>
|
||||
|
||||
## Universal styling
|
||||
|
||||
[Universal styling](./styling/index.md) is a Roblox solution to stylesheets, similar to CSS, that lets you declare and globally apply overrides to UI instance properties. This engine‑level support is the foundation for the [Style Editor](./styling/editor.md) and the end‑to‑end token pipeline.
|
||||
|
||||
## Interactive frameworks
|
||||
|
||||
In addition to the core [user interface objects](#ui-objects), the following frameworks provide built‑in and customizable interactivity for your experiences.
|
||||
|
||||
### Proximity prompts
|
||||
|
||||
[Proximity prompts](../ui/proximity-prompts.md) are unique built-in UI objects which prompt user interaction to trigger an action when they approach in-experience objects such as doors, light switches, and buttons.
|
||||
|
||||
<video src="../assets/ui/proximity-prompt/Showcase.mp4" controls width="80%" alt="Proximity prompts used in a variety of implementations in the 3D world"></video>
|
||||
|
||||
### UI drag detectors
|
||||
|
||||
[UI drag detectors](../ui/ui-drag-detectors.md) facilitate and encourage interaction with 2D user interface elements such as sliders, spinners, and more.
|
||||
|
||||
<video src="../assets/ui/ui-drag-detectors/Showcase.mp4" controls width="80%" alt="How to add UI Drag Detectors and Drag in Edit Mode"></video>
|
||||
|
||||
### 3D drag detectors
|
||||
|
||||
[3D drag detectors](../ui/3D-drag-detectors.md) encourage physical interaction with 3D objects in an experience, such as opening doors and drawers, sliding a part around, grabbing and tossing a bowling ball, pulling back and firing a slingshot, and much more.
|
||||
|
||||
<video src="../assets/ui/3D-drag-detectors/Showcase.mp4" controls width="80%" alt="Drag detectors used in a variety of implementations in the 3D world"></video>
|
||||
77
.cursor/rules/roblox/en-us/ui/labels.md
Normal file
77
.cursor/rules/roblox/en-us/ui/labels.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Text & image labels
|
||||
description: Explore how labels display customizable text and images in user interfaces.
|
||||
---
|
||||
|
||||
import TextFiltering from '../includes/text-filtering/text-filtering.md'
|
||||
|
||||
**Labels** are `Class.GuiObject|GuiObjects` that let you display customizable text and images [on‑screen](../ui/on-screen-containers.md) or [in‑experience](../ui/in-experience-containers.md). There are two types of labels:
|
||||
|
||||
- A `Class.TextLabel` is a rectangle with text that you can style through customizable properties. This is the primary way to display text in an experience.
|
||||
|
||||
- An `Class.ImageLabel` is a rectangle with an image [asset](../projects/assets/index.md) that you can style through customizable properties. Alongside [textures and decals](../parts/textures-decals.md), this lets you display images in an experience.
|
||||
|
||||
## Create labels on the screen
|
||||
|
||||
Labels on a screen are useful for things like displaying images of characters
|
||||
with dialog.
|
||||
|
||||
To add a label to a screen:
|
||||
|
||||
1. In the **Explorer** window, select **StarterGui** and add a **ScreenGui**.
|
||||
|
||||
1. Hover over StarterGui and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. From the menu, insert a **ScreenGui**.
|
||||
|
||||
2. Select the new **ScreenGui** and add a label.
|
||||
|
||||
1. Hover over **ScreenGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. From the menu, insert a **TextLabel** or **ImageLabel**.
|
||||
|
||||
<img src="../assets/ui/button-text-input/ImageButton-Screen-New.jpg" width="80%" />
|
||||
|
||||
## Create labels on part faces
|
||||
|
||||
Labels on a part are useful for things like creating billboards, posters, and
|
||||
wallpaper.
|
||||
|
||||
To add a label to the face of a part:
|
||||
|
||||
1. In the **Explorer** window, insert a **SurfaceGui** onto the part.
|
||||
|
||||
1. Hover over the part instance and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. From the menu, insert a **SurfaceGui**.
|
||||
|
||||
2. Select the new **SurfaceGui** and add a label.
|
||||
|
||||
1. Hover over the **SurfaceGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. From the menu, insert a **TextLabel** or **ImageLabel**.
|
||||
|
||||
<img src="../assets/ui/button-text-input/ImageButton-SurfaceGui-New.jpg" width="80%" />
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the button, try [choosing a different face](../parts/textures-decals.md#choose-a-face) in the <b>Face</b> property of the <b>SurfaceGui</b>.
|
||||
</Alert>
|
||||
|
||||
## Adjust ImageLabel properties
|
||||
|
||||
All images within an `Class.ImageLabel` must be assets that have been [imported](../projects/assets/manager.md#asset-import) to Studio. While the image automatically scales to fit the area of the rectangle, the image looks best when it displays at its native resolution.
|
||||
|
||||
You can customize the visual appearance of an image with the following
|
||||
properties:
|
||||
|
||||
- `Class.ImageLabel.ImageColor3|ImageColor3`
|
||||
|
||||
- `Class.ImageLabel.ImageTransparency|ImageTransparency`
|
||||
|
||||
- `Class.GuiObject.BackgroundTransparency|BackgroundTransparency`
|
||||
|
||||
For example, to display only the image and hide the rectangle, set the `Class.GuiObject.BackgroundTransparency|BackgroundTransparency` property to `1`.
|
||||
|
||||
## Text filtering
|
||||
|
||||
<TextFiltering components={props.components} context="characters/strings that are displayed on text labels" />
|
||||
127
.cursor/rules/roblox/en-us/ui/list-flex-layouts.md
Normal file
127
.cursor/rules/roblox/en-us/ui/list-flex-layouts.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
title: List and flex layouts
|
||||
description: How to use list layouts and flex settings for highly responsive user interfaces.
|
||||
---
|
||||
|
||||
The `Class.UIListLayout` positions sibling `Class.GuiObject|GuiObjects` into horizontal rows or vertical columns within their parent container. Whenever you add or remove a sibling object, the layout adjusts accordingly.
|
||||
|
||||
<img src="../assets/ui/ui-objects/UIListLayout-Example.png" width="840" />
|
||||
|
||||
<Alert severity="warning">
|
||||
Once you insert a `Class.UIListLayout`, it either overrides or influences the `Class.GuiObject.Position|Position`, `Class.GuiObject.Rotation|Rotation`, and/or `Class.GuiObject.Size|Size` of all sibling UI objects, so changes to those properties within the [Properties](../studio/properties.md) window or within a script will not have the normal effect.
|
||||
</Alert>
|
||||
|
||||
## Fill direction
|
||||
|
||||
The `Class.UIListLayout.FillDirection|FillDirection` property determines the direction in which the list layout's siblings will render.
|
||||
|
||||
<img src="../assets/engine-api/classes/UIListLayout/FillDirection.png" width="720" alt="UIListLayouts illustrating FillDirection of either horizontal or vertical." />
|
||||
|
||||
Ordering is determined by the layout's `Class.UIListLayout.SortOrder|SortOrder` property which can be either **ascending numeric**, based on each item's `Class.GuiObject.LayoutOrder|LayoutOrder` integer value, or **alphanumeric** based on the item's `Class.Instance.Name|Name`.
|
||||
|
||||
<img src="../assets/engine-api/classes/UIListLayout/SortOrder.png" width="720" alt="List layout examples illustrating numerical LayoutOrder sorting or alphanumerical Name sorting." />
|
||||
|
||||
<Alert severity="info">
|
||||
To reverse elements, such as right-to-left ordering in a horizontal list, you'll need to reverse the sorting. For example, if four items are ordered by `Class.GuiObject.LayoutOrder|LayoutOrder` of `0`, `1`, `2`, `3` respectively, negate each value to form an order of `0`, `-1`, `-2`, `-3`.
|
||||
</Alert>
|
||||
|
||||
## Alignment
|
||||
|
||||
The `Class.UIListLayout.HorizontalAlignment|HorizontalAlignment` and `Class.UIListLayout.VerticalAlignment|VerticalAlignment` properties determine the respective **X** and **Y** alignment of both the list's siblings in relation to each other and the list's overall bounds within its container. For example, a horizontally-filled list with `Class.UIListLayout.VerticalAlignment|VerticalAlignment` of `Enum.VerticalAlignment|Center` centrally aligns the list's siblings with each other **and** centers the list vertically in its container.
|
||||
|
||||
<img src="../assets/engine-api/classes/UIListLayout/VerticalAlignment-Center.png" width="720" alt="List layout illustrating VerticalAlignment of Center." />
|
||||
|
||||
<Alert severity="info">
|
||||
`Class.UIListLayout.HorizontalAlignment|HorizontalAlignment` and `Class.UIListLayout.VerticalAlignment|VerticalAlignment` differ slightly from [item line alignment](#item-line-alignment) in a [flex](#flex-layouts) layout, with which you can align items in the same line without applying the same alignment to the list's overall bounds in its container.
|
||||
</Alert>
|
||||
|
||||
## Wrapping
|
||||
|
||||
The `Class.UIListLayout.Wraps|Wraps` boolean controls whether siblings within the parent container wrap to another line when their default size exceeds the width/height of the container's bounds.
|
||||
|
||||
<img src="../assets/engine-api/classes/UIListLayout/Wraps.png" width="800" alt="Diagram showing how Wraps affects how siblings are distributed within the parent container's bounds." />
|
||||
|
||||
## Padding
|
||||
|
||||
The `Class.UIListLayout.Padding|Padding` property determines the amount of empty space **between** each list item, set to either a scale (percentage of the container's size in the current direction) or an offset (static spacing value similar to pixel size).
|
||||
|
||||
<Alert severity="info">
|
||||
This property does not apply padding **around** the list's overall bounds; see `Class.UIPadding` to apply top, bottom, left, and/or right padding to the contents of the parent.
|
||||
</Alert>
|
||||
|
||||
## Flex layouts
|
||||
|
||||
Integrating **flex** into a `Class.UIListLayout` is a powerful way to [equally fill/distribute](#equal-fill-or-distribution) or [align/stretch](#item-line-alignment) list items across their line, or [flex specific items](#flex-individual-items) across a variable space.
|
||||
|
||||
<Alert severity="warning">
|
||||
While flex is a powerful tool, you should not apply it to a `Class.UIListLayout` without purpose, as it adds a slight performance cost above non‑flex, especially when resizing the layout or dynamically adding/removing flex items.
|
||||
|
||||
Additionally, a [grid layout](../ui/grid-table-layouts.md#grid-layout) may be preferable when items should strictly align to a grid in both the **X** and **Y** directions, since grid layouts enforce a consistent cell size while flex layouts may use a variable number of items per line.
|
||||
</Alert>
|
||||
|
||||
### Equal fill or distribution
|
||||
|
||||
When the list layout's [fill direction](#fill-direction) is set to `Enum.FillDirection|Horizontal`, the `Class.UIListLayout.HorizontalFlex|HorizontalFlex` property specifies how to distribute extra horizontal space in the parent container. Similarly, when the fill direction is set to `Enum.FillDirection|Vertical`, the `Class.UIListLayout.VerticalFlex|VerticalFlex` property specifies how to distribute extra vertical space.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="HorizontalFlex">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/HorizontalFlex-Options.png" width="800" height="400" alt="UIListLayout examples showing how each HorizontalFlex option affects the size and spacing of sibling UI objects." />
|
||||
</figure>
|
||||
</TabItem>
|
||||
<TabItem label="VerticalFlex">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/VerticalFlex-Options.png" width="800" height="400" alt="UIListLayout examples showing how each VerticalFlex option affects the size and spacing of sibling UI objects." />
|
||||
</figure>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
One practical use of `Class.UIListLayout.HorizontalFlex|HorizontalFlex` is a **tabbed window** interface where flex fills the tab bar equally, regardless of the number of tabs. No approach is easier than flex in this case, because it automatically calculates the width of each tab without developer‑defined width settings, and it automatically adjusts if the number of tabs changes.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="3 Tabs">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/HorizontalFlex-Tabs-3.png" width="760" height="412" />
|
||||
</figure>
|
||||
</TabItem>
|
||||
<TabItem label="6 Tabs">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/HorizontalFlex-Tabs-6.png" width="760" height="412" />
|
||||
</figure>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Item line alignment
|
||||
|
||||
The `Class.UIListLayout.ItemLineAlignment|ItemLineAlignment` property defines the **cross‑directional** alignment of siblings within a line, letting you align objects of different widths/heights or make objects of lesser width/height fill their entire line.
|
||||
|
||||
<img src="../assets/engine-api/classes/UIListLayout/ItemLineAlignment.png" width="720" alt="Examples of options for ItemLineAlignment in a horizontal fill direction." />
|
||||
|
||||
One practical use case for `Class.UIListLayout.ItemLineAlignment|ItemLineAlignment` is to stretch a series of inconsistently sized tiles to fill their entire row (`Enum.ItemLineAlignment|Stretch`), making the layout more cohesive.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Uneven Layout">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/ItemLineAlignment-Start.png" width="760" height="404" />
|
||||
<figcaption>Tiles with varying content heights result in an uneven and staggered layout</figcaption>
|
||||
</figure>
|
||||
</TabItem>
|
||||
<TabItem label="Filled Lines">
|
||||
<figure>
|
||||
<img src="../assets/engine-api/classes/UIListLayout/ItemLineAlignment-Stretch.png" width="760" height="404" />
|
||||
<figcaption>Tiles stretched to fill their line result in a more even and consistent layout</figcaption>
|
||||
</figure>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Flex individual items
|
||||
|
||||
While flexing an entire layout is a powerful utility, certain layouts are more suited to **individual item flexing**. In such layouts, some elements in the list maintain their core size while other items flex to fill variable spaces. One practical usage is a slider bar widget with uniform [labels](../ui/labels.md) on both ends and a flexible slider bar that fills the entire width between.
|
||||
|
||||
<img src="../assets/ui/ui-objects/UIFlexItem-Example.png" width="720" alt="Example of UIFlexItem applied to a specific GuiObject under control of a UIListLayout." />
|
||||
|
||||
To set one specific list item as flexible, insert a `Class.UIFlexItem` as a child of the flexible item, then set its `Class.UIFlexItem.FlexMode|FlexMode` property to `Enum.UIFlexMode.Fill|Fill`, `Enum.UIFlexMode.Grow|Grow`, `Enum.UIFlexMode.Shrink|Shrink`, or `Enum.UIFlexMode.Custom|Custom`.
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/UIFlexItem-Hierarchy.png" width="500" alt="Example hierarchy of a UIFlexItem parented to a GuiObject under control of a UIListLayout." />
|
||||
</figure>
|
||||
100
.cursor/rules/roblox/en-us/ui/on-screen-containers.md
Normal file
100
.cursor/rules/roblox/en-us/ui/on-screen-containers.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
title: On-screen UI containers
|
||||
description: Learn how to display UI objects on a user's screen.
|
||||
---
|
||||
|
||||
import DefaultUI from '../includes/ui/default-ui.md'
|
||||
import ScreenInsets from '../includes/ui/screen-insets.md'
|
||||
|
||||
The `Class.ScreenGui` container holds `Class.GuiObject|GuiObjects` to display on a player's screen, including [frames](../ui/frames.md), [labels](../ui/labels.md), [buttons](../ui/buttons.md), and more. All on‑screen UI objects and code are stored and changed on the client.
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/ui-objects/ScreenGui-Example.jpg" width="840" alt="Example ScreenGui with various GuiObject children, including a Frame, TextLabel, TextBox, and ImageButton." />
|
||||
<Alert severity="info">
|
||||
For UI containers that hold `Class.GuiObject|GuiObjects` that you want to display **within** the 3D world, such as on the face of a part, see [In-Experience UI Containers](../ui/in-experience-containers.md).
|
||||
</Alert>
|
||||
</figure>
|
||||
|
||||
To display a `Class.ScreenGui` and its child `Class.GuiObject|GuiObjects` to every player who joins the experience, place it inside the `Class.StarterGui` container. When a player joins an experience and their character first spawns, the `Class.ScreenGui` and its contents clone into the `Class.PlayerGui` container for that player, located within the `Class.Players` container.
|
||||
|
||||
<img src="../assets/ui/ui-objects/StarterGui-To-PlayerGui.png" width="840" alt="Diagram of how a ScreenGui clones from StarterGui to a player's PlayerGui" />
|
||||
|
||||
<Alert severity="success">
|
||||
By default, `Class.GuiObject|GuiObjects` inside a `Class.ScreenGui` within `Class.StarterGui` appear as an overlay of the [3D viewport](../studio/ui-overview.md#3d-viewport), simulating their appearance and position in a running experience. To hide all such screen overlays, toggle off **GUI overlay** from the [Visualization Options](../studio/ui-overview.md#visualization-options) widget in the upper‑right corner of the 3D viewport.
|
||||
</Alert>
|
||||
|
||||
<Alert severity="info">
|
||||
If `Class.Players.CharacterAutoLoads` is disabled, the contents of `Class.StarterGui` will not be cloned until `Class.Player:LoadCharacter()` is called.
|
||||
</Alert>
|
||||
|
||||
As an experience grows in scope, you may require multiple screen interfaces such as a title screen, settings menu, shop interface, and more. In such cases, you can place multiple unique `Class.ScreenGui` containers inside `Class.StarterGui` and toggle each container's `Class.ScreenGui.Enabled|Enabled` property depending on whether it should be visible and active (while `false`, contents will not render, process user input, or update in response to changes).
|
||||
|
||||
<img src="../assets/ui/ui-objects/ScreenGuis-Enabled.png" width="760" alt="Explorer hierarchy showing multiple ScreenGui containers, one enabled and the others disabled, in order to control which are visible at a given time." />
|
||||
|
||||
The `Class.ScreenGui.Enabled|Enabled` property can be initially toggled through the [Properties](../studio/properties.md) window and/or you can set it during playtime from a client‑side script by [accessing](#access-player-ui) the player's `Class.PlayerGui` and setting it to `true` or `false` for the desired container(s).
|
||||
|
||||
<Alert severity="info">
|
||||
When using multiple `Class.ScreenGui` interfaces, you can layer them by Z‑index through their `Class.ScreenGui.DisplayOrder|DisplayOrder` property. See [Display Order](#display-order) for more information.
|
||||
</Alert>
|
||||
|
||||
## Container properties
|
||||
|
||||
The following properties let you customize the [screen insets](#screen-insets) across multiple devices, the [display order](#display-order) when using multiple screen containers, and more.
|
||||
|
||||
### Screen insets
|
||||
|
||||
<ScreenInsets components={props.components} />
|
||||
|
||||
### Display order
|
||||
|
||||
When using multiple `Class.ScreenGui` interfaces, you can layer them by Z‑index through their `Class.ScreenGui.DisplayOrder|DisplayOrder` property. For example, to display a modal settings menu on one `Class.ScreenGui` in front of the experience's main user interface on another `Class.ScreenGui`, assign a higher `Class.ScreenGui.DisplayOrder|DisplayOrder` to the modal's than the underlying interface's.
|
||||
|
||||
### Reset on spawn
|
||||
|
||||
The `Class.ScreenGui.ResetOnSpawn|ResetOnSpawn` boolean property determines if the `Class.ScreenGui` resets (deletes itself and re‑clones into the player's `Class.PlayerGui`) every time the player's character respawns.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Condition</th>
|
||||
<th>Resets</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.ScreenGui.ResetOnSpawn|ResetOnSpawn` is `true` (default).</td>
|
||||
<td><Chip label="YES" size="small" variant="outlined" color="success" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The `Class.ScreenGui` is an **indirect** descendant of `Class.StarterGui`; for example it's placed inside a `Class.Folder` located within `Class.StarterGui`.</td>
|
||||
<td><Chip label="YES" size="small" variant="outlined" color="success" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ScreenGui.ResetOnSpawn|ResetOnSpawn` is `false` **and** the `Class.ScreenGui` is a **direct** descendant of `Class.StarterGui`.</td>
|
||||
<td><Chip label="NO" size="small" variant="outlined" color="warning" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Access player UI
|
||||
|
||||
As noted, parenting a `Class.ScreenGui` to `Class.StarterGui` clones it and its child `Class.GuiObject|GuiObjects` into a player's `Class.PlayerGui` container when they join the experience and their character first spawns.
|
||||
|
||||
If you need to control a player's UI container during playtime, for example to show/hide a specific `Class.ScreenGui` or any of its children, access it as follows from a `Class.LocalScript`:
|
||||
|
||||
```lua title="LocalScript - Accessing a Player's UI"
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local player = Players.LocalPlayer
|
||||
local playerGui = player.PlayerGui
|
||||
|
||||
local titleScreen = playerGui:WaitForChild("TitleScreen")
|
||||
local settingsMenu = playerGui:WaitForChild("SettingsMenu")
|
||||
|
||||
titleScreen.Enabled = false -- Hide title screen
|
||||
settingsMenu.Enabled = true -- Show settings menu
|
||||
```
|
||||
|
||||
## Disable default UI
|
||||
|
||||
<DefaultUI components={props.components} />
|
||||
60
.cursor/rules/roblox/en-us/ui/page-layouts.md
Normal file
60
.cursor/rules/roblox/en-us/ui/page-layouts.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: Page layouts
|
||||
description: Explore how page layouts organize interface content into distinct pages.
|
||||
---
|
||||
|
||||
When you parent a `Class.UIPageLayout` to a UI container, every sibling `Class.GuiObject` becomes a unique page that you can transition to through scripting. This layout is useful when you want to create user interfaces such as tabbed modals, tutorials, or character customization screens.
|
||||
|
||||
<img src="../assets/studio/explorer/UIPageLayout.png" width="320" />
|
||||
|
||||
After you create multiple pages within the `Class.UIPageLayout`, you need to use scripting to transition from page to page. For example, the following code, pasted into a client‑side sibling `Class.Script` of the layout, transitions forward and then backward between the pages every two seconds.
|
||||
|
||||
```lua
|
||||
local frame = script.Parent
|
||||
|
||||
local pageLayout = frame:FindFirstChildWhichIsA("UIPageLayout")
|
||||
|
||||
task.wait(2)
|
||||
pageLayout:Next()
|
||||
|
||||
task.wait(2)
|
||||
pageLayout:Next()
|
||||
|
||||
task.wait(2)
|
||||
pageLayout:Previous()
|
||||
|
||||
task.wait(2)
|
||||
pageLayout:Previous()
|
||||
```
|
||||
|
||||
If you want to view pages while editing in Studio, you can use the [Command Bar](../studio/ui-overview.md#command-bar) to navigate from one page to another, letting you review where you need to make changes without having to play your experience each time.
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window hierarchy, select the `Class.UIPageLayout` object.
|
||||
|
||||
<img src="../assets/studio/explorer/UIPageLayout.png" width="320" />
|
||||
|
||||
2. Open the [Command Bar](../studio/ui-overview.md#command-bar).
|
||||
3. Input any of the following commands and press <kbd>Enter</kbd>.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Action</td>
|
||||
<td>Command</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>**Next Page**</td>
|
||||
<td>`game:GetService("Selection"):Get()[1]:Next()`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**Previous Page**</td>
|
||||
<td>`game:GetService("Selection"):Get()[1]:Previous()`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>**First Page**</td>
|
||||
<td>`game:GetService("Selection"):Get()[1]:JumpToIndex(0)`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
207
.cursor/rules/roblox/en-us/ui/position-and-size.md
Normal file
207
.cursor/rules/roblox/en-us/ui/position-and-size.md
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
---
|
||||
title: Position and size UI objects
|
||||
description: Explains the process of positioning, sizing, and ordering UI objects on a player's screen.
|
||||
---
|
||||
|
||||
Unless UI objects are under control of a [layout structure](#layout-structures) or a [size modifier/constraint](../ui/size-modifiers.md), you have complete control over their [position](#position) and [size](#size). You can also set the Z‑index [layering](#zindex) order in which objects overlap.
|
||||
|
||||
## Core properties
|
||||
|
||||
All `Class.GuiObject|GuiObjects` share a core set of properties to [position](#position), [size](#size), [anchor](#anchorpoint), and [layer](#zindex) them within an on‑screen or in‑experience container.
|
||||
|
||||
### Position
|
||||
|
||||
The `Class.GuiObject.Position|Position` property is a `Datatype.UDim2` coordinate set that positions the object along the **X** and **Y** axes. A `Datatype.UDim2` is represented by both `Datatype.UDim.Scale|Scale` and `Datatype.UDim.Offset|Offset` values for each axis:
|
||||
|
||||
- `Datatype.UDim.Scale|Scale` values represent a **percentage** of the container's size along the corresponding axis, additive of any `Datatype.UDim.Offset|Offset` values.
|
||||
- `Datatype.UDim.Offset|Offset` values represent how many **pixels** to shift the object on the corresponding axis, additive of any `Datatype.UDim.Scale|Scale` values.
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/misc/UDim2-Components.png" width="334" />
|
||||
</figure>
|
||||
|
||||
To edit the position of a selected `Class.GuiObject`, click the **Position** field in the [Properties](../studio/properties.md) window and enter a new `Datatype.UDim2` coordinate set.
|
||||
|
||||
<img src="../assets/studio/properties/GuiObject-Position.png" width="320" />
|
||||
|
||||
<img src="../assets/ui/misc/Scale-Offset-Positioning.png" width="840" />
|
||||
|
||||
<Alert severity="success">
|
||||
Brackets and spaces are **optional** when entering a `Datatype.UDim2` in Studio — you can simply enter the four values separated by commas, for instance `0.25,40,0.1,20`, and Studio will infer the intended value set.
|
||||
|
||||
Studio also infers the intended value set when a single number is entered. For example, entering simply `0.5` will be converted to <Typography noWrap>`{0.5, 0},{0.5, 0}`</Typography> since `0.5` is likely a **Scale** value of 50%. Conversely, entering `20` will be converted to <Typography noWrap>`{0, 20},{0, 20}`</Typography> since `20` is likely an **Offset** value of 20 pixels and not a scale of 2000%.
|
||||
</Alert>
|
||||
|
||||
### Size
|
||||
|
||||
The `Class.GuiObject.Size|Size` property is a `Datatype.UDim2` coordinate set that sizes the object along the **X** and **Y** axes. A `Datatype.UDim2` is represented by both `Datatype.UDim.Scale|Scale` and `Datatype.UDim.Offset|Offset` values for each axis:
|
||||
|
||||
- **Scale** — Values that represent a **percentage** of the container's size along the corresponding axis, additive to any **Offset** values.
|
||||
- **Offset** — Values that represent the object's **pixel** size along the corresponding axis, additive to any **Scale** values.
|
||||
|
||||
To edit the size of a selected `Class.GuiObject`, click the **Size** field in the [Properties](../studio/properties.md) window and enter a new `Datatype.UDim2` coordinate set.
|
||||
|
||||
<img src="../assets/studio/properties/GuiObject-Size.png" width="320" />
|
||||
|
||||
<img src="../assets/ui/misc/Scale-Sizing.png" width="840" />
|
||||
|
||||
<Alert severity="success">
|
||||
Brackets and spaces are **optional** when entering a `Datatype.UDim2` in Studio — you can simply enter the four values separated by commas, for instance `0.75,0,0.25,0`, and Studio will infer the intended value set.
|
||||
|
||||
Studio also infers the intended value set when a single number is entered. For example, entering simply `0.5` will be converted to <Typography noWrap>`{0.5, 0},{0.5, 0}`</Typography> since `0.5` is likely a **Scale** value of 50%. Conversely, entering `20` will be converted to <Typography noWrap>`{0, 20},{0, 20}`</Typography> since `20` is likely an **Offset** value of 20 pixels and not a scale of 2000%.
|
||||
</Alert>
|
||||
|
||||
### AnchorPoint
|
||||
|
||||
The `Class.GuiObject.AnchorPoint|AnchorPoint` property defines the **origin point** from where the object changes [position](#position) and [size](#size). The default `Class.GuiObject.AnchorPoint|AnchorPoint` values are <Typography noWrap>`(0, 0)`</Typography> which places the anchor in the top‑left corner of the object.
|
||||
|
||||
`Class.GuiObject.AnchorPoint|AnchorPoint` values are a **fraction** from `0` to `1`, relative to the [size](#size) of the object, meaning an object with `Class.GuiObject.AnchorPoint|AnchorPoint` values of <Typography noWrap>`(0.5, 0.5)`</Typography> places the anchor point halfway (50%) through the object both horizontally and vertically, and any changes to its [position](#position) or [size](#size) both move and scale outward from this point.
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<img src="../assets/ui/misc/Anchor-Points.png" width="400" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../assets/ui/misc/Anchor-Point-Scaling.png" width="400" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
To view and edit the anchor point of a selected `Class.GuiObject`:
|
||||
|
||||
1. In the [Properties](../studio/properties.md) window, click inside the **AnchorPoint** field.
|
||||
|
||||
<img src="../assets/studio/properties/GuiObject-AnchorPoint.png" width="320" />
|
||||
|
||||
2. Enter a new `Datatype.Vector2` coordinate and press <kbd>Enter</kbd>.
|
||||
|
||||
<Alert severity="success">
|
||||
Studio infers the intended value pair when a single number is entered. For example, entering simply `0.5` will be converted to <Typography noWrap>`0.5, 0.5`</Typography> for a center anchor point.
|
||||
</Alert>
|
||||
|
||||
### ZIndex
|
||||
|
||||
The `Class.GuiObject.ZIndex|ZIndex` property defines the layer order in which `Class.GuiObject|GuiObjects` render and overlap each other. If you want to create new rendering layers, you must set the `Class.GuiObject.ZIndex|ZIndex` property to different positive or negative integer values for each object.
|
||||
|
||||
For UI containers like `Class.ScreenGui`, the default `Class.ScreenGui.ZIndexBehavior|ZIndexBehavior` always renders children above their parents, and each child's `Class.GuiObject.ZIndex|ZIndex` is used to decide the order in which it renders over others.
|
||||
|
||||
To edit an object's `Class.GuiObject.ZIndex|ZIndex`, locate **ZIndex** in the [Properties](../studio/properties.md) window and enter a new integer value.
|
||||
|
||||
<img src="../assets/studio/properties/GuiObject-ZIndex.png" width="320" />
|
||||
|
||||
## Layout structures
|
||||
|
||||
Layout structures let you quickly organize and display `Class.GuiObject|GuiObjects`, for example into a horizontal or vertical [list](../ui/list-flex-layouts.md), a [grid](../ui/grid-table-layouts.md#grid-layout) of equally‑sized tiles, a [page sequence](../ui/page-layouts.md), and more. Layouts typically override or influence the [position](#position)/[size](#size) of objects under their control.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Layout</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>[List](../ui/grid-table-layouts.md#grid-layout)</td>
|
||||
<td>`Class.UIListLayout` positions sibling `Class.GuiObject|GuiObjects` into horizontal rows or vertical columns within their parent container.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Grid](../ui/grid-table-layouts.md#grid-layout)</td>
|
||||
<td>`Class.UIGridLayout` positions sibling `Class.GuiObject|GuiObjects` in a grid of uniform cells of the same size within their parent container.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Table](../ui/grid-table-layouts.md#table-layout)</td>
|
||||
<td>`Class.UITableLayout` positions sibling `Class.GuiObject|GuiObjects` and their children into table format.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[Page](../ui/page-layouts.md)</td>
|
||||
<td>`Class.UIPageLayout` organizes its sibling `Class.GuiObject|GuiObjects` into unique pages that you can transition to through scripting.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Cross-platform factors
|
||||
|
||||
Roblox is inherently [cross‑platform](../projects/cross-platform.md), as players can discover and join experiences on their phone or tablet, then later continue where they left off on their PC or console. You should design your Roblox experiences to be accessible and enjoyable on **all** platforms that you choose to support, instead of optimizing for one platform and neglecting others.
|
||||
|
||||
### Reserved zones
|
||||
|
||||
On mobile devices, the default controls occupy a portion of the bottom-left and bottom-right corners of the screen. When you design an experience's UI, avoid placing important info or virtual buttons in these zones.
|
||||
|
||||
<img src="../assets/ui/misc/Touch-Reserved-Zones.png" width="840" />
|
||||
|
||||
### Thumb zones
|
||||
|
||||
Most mobile players use two thumbs — one on the virtual thumbstick and one on the jump button. Depending on the physical size of the device and the player's hands, reaching too far from the bottom corners becomes uncomfortable or impossible, so you should avoid placing frequently‑used buttons outside of easy‑to‑reach zones.
|
||||
|
||||
<Grid container spacing={3} alignItems="flex-end">
|
||||
<Grid item XSmall={12} Medium={12} Large={12} XLarge={6} XXLarge={6}>
|
||||
<img src="../assets/ui/misc/Thumb-Zones-Phone-Reachable.png" width="100%" style={{marginBottom:"0px;"}} />
|
||||
<Alert severity="success">
|
||||
Button comfortably within reach of player's right thumb
|
||||
</Alert>
|
||||
</Grid>
|
||||
<Grid item XSmall={12} Medium={12} Large={12} XLarge={6} XXLarge={6}>
|
||||
<img src="../assets/ui/misc/Thumb-Zones-Phone-Non-Reachable.png" width="100%" style={{marginBottom:"0px;"}} />
|
||||
<Alert severity="warning">
|
||||
Button difficult to reach unless player stretches hand or thumb
|
||||
</Alert>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
|
||||
Remember that comfortable thumb zones differ between phones and tablets because tablets have a larger screen. A button placed 40% below the screen's top edge is reachable on a phone but almost unreachable on a tablet.
|
||||
|
||||
<Grid container spacing={3} alignItems="flex-end">
|
||||
<Grid item XSmall={12} Medium={12} Large={12} XLarge={6} XXLarge={6}>
|
||||
<img src="../assets/ui/misc/Thumb-Zones-Tablet-Reachable.png" width="100%" style={{marginBottom:"0px;"}} />
|
||||
<Alert severity="success">
|
||||
Button 40% from top edge of phone, within reach of player's thumb
|
||||
</Alert>
|
||||
</Grid>
|
||||
<Grid item XSmall={12} Medium={12} Large={12} XLarge={6} XXLarge={6}>
|
||||
<img src="../assets/ui/misc/Thumb-Zones-Tablet-Non-Reachable.png" width="100%" style={{marginBottom:"0px;"}} />
|
||||
<Alert severity="warning">
|
||||
Button 40% from top edge of tablet, difficult to reach without stretching
|
||||
</Alert>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
A reliable approach on both phones and tablets is **relative** positioning of custom buttons near frequently‑used controls like the default jump button, placing them within easy reach.
|
||||
|
||||
<img src="../assets/ui/misc/Custom-Buttons-Near-Jump.png" width="840" />
|
||||
|
||||
The following code, placed in a client-side script within `Class.StarterPlayerScripts`, fetches the position of the jump button and creates a placeholder [button](../ui/buttons.md) 20 pixels to its left.
|
||||
|
||||
```lua title="Client Script - Custom Button Near Jump Button"
|
||||
local Players = game:GetService("Players")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local player = Players.LocalPlayer
|
||||
local playerGui = player:WaitForChild("PlayerGui")
|
||||
|
||||
if UserInputService.TouchEnabled then
|
||||
-- Wait for jump button to be fully loaded
|
||||
while not (playerGui:FindFirstChild("JumpButton", true) and playerGui:FindFirstChild("JumpButton", true).IsLoaded) do
|
||||
task.wait()
|
||||
end
|
||||
local jumpButton = playerGui:FindFirstChild("JumpButton", true)
|
||||
|
||||
-- Place new custom button to left of jump button
|
||||
local customButton = Instance.new("ImageButton")
|
||||
customButton.AnchorPoint = Vector2.new(1, 1)
|
||||
customButton.Size = UDim2.fromOffset(jumpButton.Size.X.Offset * 0.8, jumpButton.Size.Y.Offset * 0.8)
|
||||
customButton.Position = jumpButton.Position + UDim2.fromOffset(-20, jumpButton.Size.Y.Offset)
|
||||
customButton.Parent = jumpButton.Parent
|
||||
else
|
||||
warn("Device is not touch-enabled or Studio is not emulating a touch-enabled device!")
|
||||
end
|
||||
```
|
||||
|
||||
### Context-based UI
|
||||
|
||||
Screen space is limited on mobile devices, so you should show only the most vital information during active gameplay. For example, if your experience includes a special input action to open doors and treasure chests, it doesn't make sense to constantly show an "open" button on the screen. Instead, use a [proximity prompt](../ui/proximity-prompts.md) or similar method to accept input only when the character approaches a door or chest.
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/misc/Contextual-Button.png" width="840" />
|
||||
<figcaption>Custom button that you display only when character is near a door or chest</figcaption>
|
||||
</figure>
|
||||
179
.cursor/rules/roblox/en-us/ui/proximity-prompts.md
Normal file
179
.cursor/rules/roblox/en-us/ui/proximity-prompts.md
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
---
|
||||
title: Proximity prompts
|
||||
description: Proximity Prompts allow users to trigger actions when they approach objects in the 3D space.
|
||||
---
|
||||
|
||||
`Class.ProximityPrompt` objects
|
||||
encourage user interaction to trigger an action when they approach in-experience
|
||||
objects such as doors, light switches, and buttons. Using this object, you can:
|
||||
|
||||
- Indicate what objects a user can interact with in the experience.
|
||||
- Display the action a user can take on the object, then trigger the action through user input such as pressing or holding a key.
|
||||
- Display the correct input for all input types, such a keyboard, gamepad, and touchscreen keys.
|
||||
|
||||
<video src="../assets/ui/proximity-prompt/Showcase.mp4" controls width="100%" alt="User interacting with proximity prompts to perform actions in the experience"></video>
|
||||
|
||||
<Alert severity="info">
|
||||
See the [Dungeon Delve](https://www.roblox.com/games/6074153281/Dungeon-Delve) sample place for the fully working proximity prompt examples shown in the video above.
|
||||
</Alert>
|
||||
|
||||
## Create proximity prompts
|
||||
|
||||
You must parent proximity prompts to the part, model, or attachment that you want a user to interact with. To add a proximity prompt to a `Class.BasePart`, `Class.Model`, or `Class.Attachment` object:
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, hover over the `Class.BasePart`, `Class.Model`, or `Class.Attachment` and click the ⊕ button. A contextual menu displays.
|
||||
1. From the menu, insert a **ProximityPrompt**.
|
||||
|
||||
<img src="../assets/ui/proximity-prompt/ProximityPrompt-New.png" width="320" alt="Explorer hierarchy showing a ProximityPrompt parented to an Attachment" />
|
||||
|
||||
## Customize proximity prompts
|
||||
|
||||
You can customize a proximity prompt based on how you want it to [appear](#appearance), when you want it to be [visible](#visibility), and what you want a user to do to [trigger the action](#interactivity).
|
||||
|
||||
### Appearance
|
||||
|
||||
Proximity prompts need to communicate three things:
|
||||
|
||||
- The **object** that a user can interact with.
|
||||
- The **action** that triggers when they interact with the proximity
|
||||
prompt.
|
||||
- The **key** that a user must press or hold.
|
||||
|
||||
You can specify these through the following properties:
|
||||
|
||||
- `Class.ProximityPrompt.ObjectText|ObjectText` An optional name for the object a user can interact with.
|
||||
- `Class.ProximityPrompt.ActionText|ActionText` An optional name for the action a user will trigger.
|
||||
- `Class.ProximityPrompt.KeyboardKeyCode|KeyboardKeyCode` The keyboard key a user must press or hold to trigger the action.
|
||||
- `Class.ProximityPrompt.GamepadKeyCode|GamepadKeyCode` The gamepad key a user must press or hold to trigger the action.
|
||||
|
||||
<img src="../assets/ui/proximity-prompt/Prompt-Diagram.png" width="600" alt="Diagram indicating basic elements of a ProximityPrompt" />
|
||||
|
||||
### Visibility
|
||||
|
||||
You can control when the proximity prompt is visible through its `Class.ProximityPrompt.MaxActivationDistance|MaxActivationDistance`, `Class.ProximityPrompt.RequiresLineOfSight|RequiresLineOfSight`, and `Class.ProximityPrompt.Exclusivity|Exclusivity` properties.
|
||||
|
||||
#### MaxActivationDistance
|
||||
|
||||
The `Class.ProximityPrompt.MaxActivationDistance|MaxActivationDistance` property allows you to define the range from around the `Class.ProximityPrompt` object that activates the visibility of the proximity prompt. Once a user's character enters that range, the proximity prompt becomes visible.
|
||||
|
||||
<img src="../assets/ui/proximity-prompt/MaxActivationDistance.jpg" width="800" alt="Diagram indicating how a character's distance from a ProximityPrompt object affects whether the prompt appears on screen" />
|
||||
|
||||
#### RequiresLineOfSight
|
||||
|
||||
The `Class.ProximityPrompt.RequiresLineOfSight|RequiresLineOfSight` property activates the visibility of the proximity prompt when there's a clear path from the **camera** to the `Class.ProximityPrompt` object. By default, this property is set to true.
|
||||
|
||||
<video src="../assets/ui/proximity-prompt/RequiresLineOfSight.mp4" controls width="800" alt="Video showing how player's line of sight affects the appearance of a ProximityPrompt on screen"></video>
|
||||
|
||||
#### Exclusivity
|
||||
|
||||
If a user's character is within range of multiple proximity prompts, each proximity prompt's visibility depends on which proximity prompt the camera is pointing at, as well as each proximity prompt's `Class.ProximityPrompt|Exclusivity` property value.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="OnePerButton">
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/proximity-prompt/Exclusivity-OnePerButton.jpg" width="800" alt="ProximityPrompt objects set to exclusivity of OnePerButton" />
|
||||
<figcaption>Only one proximity prompt is visible per input keycode. If multiple in-range proximity prompts use different keycodes, they are all visible. However, if multiple in-range proximity prompts use the same keycode, only one proximity prompt is visible at any given time, depending on the camera's view direction.</figcaption>
|
||||
</figure>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="OneGlobally">
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/proximity-prompt/Exclusivity-OneGlobally.jpg" width="800" alt="ProximityPrompt objects set to exclusivity of OneGlobally" />
|
||||
<figcaption>If multiple proximity prompts are set to this exclusivity, only one prompt is visible at any given time, regardless of the character's proximity or the prompt's keycode. The camera's view direction determines which proximity prompt is visible, useful for minimizing prompt distraction when there are several in an area.</figcaption>
|
||||
</figure>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="AlwaysShow">
|
||||
|
||||
<figure>
|
||||
<img src="../assets/ui/proximity-prompt/Exclusivity-AlwaysShow.jpg" width="800" alt="ProximityPrompt objects set to exclusivity of AlwaysShow" />
|
||||
<figcaption>Proximity prompts are always visible when in range, assuming no other factors prevent them from being visible. Be cautious when using this setup, as all visible prompts using the same keycode will trigger on a player's input of that keycode.</figcaption>
|
||||
</figure>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Interactivity
|
||||
|
||||
You can customize how a user interacts with a proximity prompt through its `Class.ProximityPrompt.HoldDuration|HoldDuration` and `Class.ProximityPrompt.ClickablePrompt|ClickablePrompt` properties.
|
||||
|
||||
#### HoldDuration
|
||||
|
||||
The `Class.ProximityPrompt.HoldDuration|HoldDuration` property determines how many seconds a user has to press a key before the proximity prompt's action triggers. If this property has a value of `0`, the proximity prompt's action triggers immediately.
|
||||
|
||||
<video src="../assets/ui/proximity-prompt/HoldDuration.mp4" controls width="800" alt="Video of a proximity prompt's gauge increasing as the player holds down its associated input"></video>
|
||||
|
||||
#### ClickablePrompt
|
||||
|
||||
The `Class.ProximityPrompt.ClickablePrompt|ClickablePrompt` property specifies if a user can click on a proximity prompt to trigger its action. When set to **true**, a user can interact with the proximity prompt by directly clicking the proximity prompt **or** by pressing the specified key. When set to **false**, a user can only interact with the proximity prompt by pressing the specified key.
|
||||
|
||||
<Alert severity="warning">
|
||||
Note that if a user is using a phone or a tablet, they can always interact with the proximity prompt by directly clicking the proximity prompt regardless of the `Class.ProximityPrompt.ClickablePrompt|ClickablePrompt` property's value.
|
||||
</Alert>
|
||||
|
||||
## Script proximity prompts
|
||||
|
||||
You can connect to proximity prompt events either on the `Class.ProximityPrompt` object itself or globally through `Class.ProximityPromptService`. The `Class.ProximityPromptService` allows you to manage all proximity prompt behavior from one location, preventing any need for duplicate code in your experience.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptTriggered|PromptTriggered`</td>
|
||||
<td>Fires when a player interacts with a proximity prompt (after the duration for a prompt with non-zero `Class.ProximityPrompt.HoldDuration|HoldDuration`).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptTriggerEnded|PromptTriggerEnded`</td>
|
||||
<td>Triggers when the player stops interacting with a proximity prompt.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptButtonHoldBegan|PromptButtonHoldBegan`</td>
|
||||
<td>Fires when a player begins interacting with a proximity prompt with a non-zero `Class.ProximityPrompt.HoldDuration|HoldDuration` value.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptButtonHoldEnded|PromptButtonHoldEnded`</td>
|
||||
<td>Fires when a player stops interacting with a proximity prompt with a non-zero `Class.ProximityPrompt.HoldDuration|HoldDuration` value.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptShown|PromptShown`</td>
|
||||
<td>Triggers in `Class.LocalScript|LocalScripts` when a proximity prompt is made visible.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ProximityPromptService.PromptHidden|PromptHidden`</td>
|
||||
<td>Triggers in `Class.LocalScript|LocalScripts` when a prompt is hidden.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
The following code sample includes a basic framework for using `Class.ProximityPromptService`:
|
||||
|
||||
```lua
|
||||
local ProximityPromptService = game:GetService("ProximityPromptService")
|
||||
|
||||
-- Detect when prompt is triggered
|
||||
local function onPromptTriggered(promptObject, player)
|
||||
|
||||
end
|
||||
|
||||
-- Detect when prompt hold begins
|
||||
local function onPromptHoldBegan(promptObject, player)
|
||||
|
||||
end
|
||||
|
||||
-- Detect when prompt hold ends
|
||||
local function onPromptHoldEnded(promptObject, player)
|
||||
|
||||
end
|
||||
|
||||
-- Connect prompt events to handling functions
|
||||
ProximityPromptService.PromptTriggered:Connect(onPromptTriggered)
|
||||
ProximityPromptService.PromptButtonHoldBegan:Connect(onPromptHoldBegan)
|
||||
ProximityPromptService.PromptButtonHoldEnded:Connect(onPromptHoldEnded)
|
||||
```
|
||||
285
.cursor/rules/roblox/en-us/ui/rich-text.md
Normal file
285
.cursor/rules/roblox/en-us/ui/rich-text.md
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
---
|
||||
title: Rich text markup
|
||||
description: Rich Text Markup are simple markup tags to style sections of a string.
|
||||
comments: |
|
||||
1. Replace numbered footnotes with dedicated style.
|
||||
---
|
||||
|
||||
UI **rich text** utilizes simple markup tags to style sections of a string in bold, italics, underline, fill color, stroke variations, and more. You can apply styling tags to `Class.TextLabel`, `Class.TextButton`, and `Class.TextBox` objects.
|
||||
|
||||
## Enable rich text
|
||||
|
||||
You must enable rich text on a per-object basis through its **RichText** property in the [Properties](../studio/properties.md) window, or by setting the property to `true` in a `Class.LocalScript`.
|
||||
|
||||
<img src="../assets/ui/rich-text/Enable-Rich-Text.png"
|
||||
width="320" />
|
||||
|
||||
```lua highlight="2"
|
||||
local title = Instance.new("TextLabel")
|
||||
title.RichText = true
|
||||
|
||||
title.Text = "Use a <b>bold title</b>"
|
||||
```
|
||||
|
||||
<Alert severity="info">
|
||||
When editing an object's **Text** property in Studio, toggling the **RichText** checkbox in the [Properties](../studio/properties.md) window displays the text string as a final render. This is useful for identifying and correcting mistakes in any [supported tags](#supported-tags).
|
||||
</Alert>
|
||||
|
||||
<Alert severity="warning">
|
||||
<a href="../production/localization/index.md">Localizing</a> an experience to support other languages removes rich text formatting tags. To ensure formatting appears in other languages, re-apply the tags manually to your localized strings.
|
||||
</Alert>
|
||||
|
||||
## Supported tags
|
||||
|
||||
Rich text tags are similar to XML/HTML tags and you must include both an opening and closing tag around the formatted text.
|
||||
|
||||
`<b>Formatted text</b>`
|
||||
|
||||
You can also nest tags inside each other as long as you close them in the reverse order of how you opened them.
|
||||
|
||||
`<b><i><u>Formatted text</u></i></b>`
|
||||
|
||||
### Font color
|
||||
|
||||
`<font color=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`I want the <font color="#FF7800">orange</font> candy.`<br />
|
||||
`I want the <font color="rgb(255,125,0)">orange</font> candy.`
|
||||
<img src="../assets/ui/rich-text/Example-Color.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Font size
|
||||
|
||||
`<font size=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`<font size="40">This is big.</font> <font size="20">This is small.</font>`
|
||||
<img src="../assets/ui/rich-text/Example-Size.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Font face
|
||||
|
||||
`<font face=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`<font face="Michroma">This is Michroma face.</font>`
|
||||
<img src="../assets/ui/rich-text/Example-Font-Face.png" width="600" />
|
||||
</blockquote>
|
||||
<Alert severity="info">
|
||||
Font face/family names are listed on the `Datatype.Font` enum reference page.
|
||||
</Alert>
|
||||
|
||||
### Font family
|
||||
|
||||
`<font family=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`<font family="rbxasset://fonts/families/Michroma.json">This is Michroma face.</font>`
|
||||
<img src="../assets/ui/rich-text/Example-Font-Face.png" width="600" />
|
||||
</blockquote>
|
||||
<Alert severity="info">
|
||||
Font face/family names are listed on the `Datatype.Font` enum reference page.
|
||||
</Alert>
|
||||
|
||||
### Font weight
|
||||
|
||||
`<font weight=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`This is normal. <font weight="heavy">This is heavy.</font>`<br />
|
||||
`This is normal. <font weight="900">This is heavy.</font>`
|
||||
<img src="../assets/ui/rich-text/Example-Weight.png" width="600" />
|
||||
</blockquote>
|
||||
<Alert severity="info">
|
||||
Font weight can be a case-insensitive string name including `Thin`, `ExtraLight`, `Light`, `Regular`, `Medium`, `SemiBold`, `Bold`, `ExtraBold`, or `Heavy`; it can also be a number in factors of 100 between `100` and `900`.
|
||||
</Alert>
|
||||
|
||||
### Font transparency
|
||||
|
||||
`<font transparency=""> </font>`
|
||||
|
||||
<blockquote>
|
||||
`Text at <font transparency="0.5">50% transparency</font>.`
|
||||
<img src="../assets/ui/rich-text/Example-Transparency.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Stroke
|
||||
|
||||
`<stroke> </stroke>`
|
||||
|
||||
<table size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Equivalent Property</th>
|
||||
<th>Example Values</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`color`</td>
|
||||
<td>`Class.UIStroke.Color`</td>
|
||||
<td>`"rgb(255,125,0)"`, `"#FF7800"`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`thickness` or `th`</td>
|
||||
<td>`Class.UIStroke.Thickness`</td>
|
||||
<td>`"1"`, `"4"`, `"0.5"`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`transparency` or `tr`</td>
|
||||
<td>`Class.UIStroke.Transparency`</td>
|
||||
<td>`"0"`, `"0.5"`, `"1"`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`joins`</td>
|
||||
<td>`Class.UIStroke.LineJoinMode`</td>
|
||||
<td>`"round"`, `"bevel"`, `"miter"`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`sizing`</td>
|
||||
<td>`Class.UIStroke.StrokeSizingMode`</td>
|
||||
<td>`"fixed"`, `"scaled"`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<blockquote>
|
||||
`You won <stroke color="#00A2FF" thickness="2" transparency="0.25" joins="miter">25 gems</stroke>.`
|
||||
<img src="../assets/ui/rich-text/Example-Stroke.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
<Alert severity="info">
|
||||
See [UI appearance modifiers](../ui/appearance-modifiers.md#stroke) for details on `<stroke>` tag parameters such as `thickness` and `joins`.
|
||||
</Alert>
|
||||
|
||||
### Bold
|
||||
|
||||
`<b> </b>`
|
||||
|
||||
<blockquote>
|
||||
`Text in <b>bold</b>.`
|
||||
<img src="../assets/ui/rich-text/Example-Bold.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Italic
|
||||
|
||||
`<i> </i>`
|
||||
|
||||
<blockquote>
|
||||
`Text <i>italicized</i>.`
|
||||
<img src="../assets/ui/rich-text/Example-Italic.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Underline
|
||||
|
||||
`<u> </u>`
|
||||
|
||||
<blockquote>
|
||||
`Text <u>underlined</u>.`
|
||||
<img src="../assets/ui/rich-text/Example-Underline.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Strikethrough
|
||||
|
||||
`<s> </s>`
|
||||
|
||||
<blockquote>
|
||||
`Text with <s>strikethrough</s> applied.`
|
||||
<img src="../assets/ui/rich-text/Example-Strikethrough.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Line break
|
||||
|
||||
`<br/>`
|
||||
|
||||
<blockquote>
|
||||
`New line occurs after this sentence.<br/>Next sentence...`
|
||||
<img src="../assets/ui/rich-text/Example-Line-Break.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Uppercase
|
||||
|
||||
`<uppercase> </uppercase>`<br/>`<uc> </uc>`
|
||||
|
||||
<blockquote>
|
||||
`<uppercase>Uppercase</uppercase> makes words read loudly!`<br />
|
||||
`<uc>Uppercase</uc> makes words read loudly!`
|
||||
<img src="../assets/ui/rich-text/Example-Uppercase.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Small caps
|
||||
|
||||
`<smallcaps> </smallcaps>`<br/>`<sc> </sc>`
|
||||
|
||||
<blockquote>
|
||||
`My name is <smallcaps>Diva Dragonslayer</smallcaps>.`<br />
|
||||
`My name is <sc>Diva Dragonslayer</sc>.`
|
||||
<img src="../assets/ui/rich-text/Example-Small-Caps.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Mark
|
||||
|
||||
`<mark> </mark>`
|
||||
|
||||
<blockquote>
|
||||
`Text <mark color="#009966" transparency="0">highlighted</mark>.`
|
||||
<img src="../assets/ui/rich-text/Example-Highlighted.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
### Comment
|
||||
|
||||
`<!-- -->`
|
||||
|
||||
<blockquote>
|
||||
`After this is a comment...<!--This does not appear in the final text--> and now more text...`
|
||||
<img src="../assets/ui/rich-text/Example-Comment.png" width="600" />
|
||||
</blockquote>
|
||||
|
||||
## Escape forms
|
||||
|
||||
If you want to render certain characters like `<` or `>` and exempt them from consideration as part of rich text tags, you can write them in their **escape form**.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Character</th>
|
||||
<th>Escape</th>
|
||||
<th>Example</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`<`</td>
|
||||
<td>`<`</td>
|
||||
<td>`10 < 100`</td>
|
||||
<td>10 < 100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`>`</td>
|
||||
<td>`>`</td>
|
||||
<td>`100 > 10`</td>
|
||||
<td>100 > 10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`"`</td>
|
||||
<td>`"`</td>
|
||||
<td>`Meet "Diva Dragonslayer"`</td>
|
||||
<td>Meet "Diva Dragonslayer"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`'`</td>
|
||||
<td>`'`</td>
|
||||
<td>`Diva's pet is a falcon!`</td>
|
||||
<td>Diva's pet is a falcon!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`&`</td>
|
||||
<td>`&`</td>
|
||||
<td>`Render another escape form <b>&lt;</b> by escaping an ampersand`</td>
|
||||
<td>Render another escape form **&lt;** by escaping an ampersand</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
74
.cursor/rules/roblox/en-us/ui/scrolling-frames.md
Normal file
74
.cursor/rules/roblox/en-us/ui/scrolling-frames.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
title: Scrolling frames
|
||||
description: ScrollingFrames feature built-in scrolling interactivity and are ideal for displaying a lot of information in a confined space.
|
||||
---
|
||||
|
||||
A `Class.ScrollingFrame` consists of a customizable **canvas** and **scroll bars** with built‑in scrolling interactivity and different ways to customize how the scrolling works. `Class.ScrollingFrame` is ideal for displaying a lot of information in a confined space and it works well with [list](../ui/list-flex-layouts.md) and [grid](../ui/grid-table-layouts.md) layouts.
|
||||
|
||||
<img src="../assets/ui/ui-objects/ScrollingFrame-Example.jpg" width="840" alt="Example ScrollingFrame on the screen containing a tabbed category bar and a list of magical items for the player to consider purchasing." />
|
||||
|
||||
## Canvas
|
||||
|
||||
The **canvas** is the primary area of a `Class.ScrollingFrame` that can contain other `Class.GuiObject|GuiObjects`. Scrolling behavior automatically adapts in the following scenarios:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Frame Setup</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>The frame's `Class.ScrollingFrame.CanvasSize|CanvasSize` is taller than its overall height; specifically the total `Class.ScrollingFrame.CanvasSize|CanvasSize.Y` exceeds the total `Class.ScrollingFrame.Size|Size.Y`.</td>
|
||||
<td>Vertical scrolling is enabled and a vertical scroll bar appears.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The frame's `Class.ScrollingFrame.CanvasSize|CanvasSize` is wider than its overall width; specifically the total `Class.ScrollingFrame.CanvasSize|CanvasSize.X` exceeds the total `Class.ScrollingFrame.Size|Size.X`.</td>
|
||||
<td>Horizontal scrolling is enabled and a horizontal scroll bar appears.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The frame's `Class.ScrollingFrame.AutomaticCanvasSize|AutomaticCanvasSize` is set to `Enum.AutomaticSize|Y` or `Enum.AutomaticSize|XY` and the total height of its contents (child `Class.GuiObject|GuiObjects`) exceed its total `Class.ScrollingFrame.Size|Size.Y`.</td>
|
||||
<td>Vertical scrolling is enabled and a vertical scroll bar appears.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>The frame's `Class.ScrollingFrame.AutomaticCanvasSize|AutomaticCanvasSize` is set to `Enum.AutomaticSize|X` or `Enum.AutomaticSize|XY` and the total width of its contents (child `Class.GuiObject|GuiObjects`) exceed its total `Class.ScrollingFrame.Size|Size.X`.</td>
|
||||
<td>Horizontal scrolling is enabled and a horizontal scroll bar appears.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
The `Class.ScrollingFrame.CanvasPosition|CanvasPosition` property reflects the **current** positional offset of the canvas within the frame, in pixels, and it sets the position of scroll bars accordingly. Note that this property doesn't do anything if scroll bars aren't visible.
|
||||
|
||||
## Insets
|
||||
|
||||
A scrolling frame's `Class.ScrollingFrame.CanvasSize|CanvasSize` may be affected by the `Class.ScrollingFrame.VerticalScrollBarInset|VerticalScrollBarInset` and/or `Class.ScrollingFrame.HorizontalScrollBarInset|HorizontalScrollBarInset` properties. In the following example diagram, a vertically scrolling frame uses a canvas width of 100%. With `Class.ScrollingFrame.VerticalScrollBarInset|VerticalScrollBarInset` set to `Enum.ScrollBarInset|None` (default), the canvas extends to the full width of the scrolling frame, obscuring a slight amount of content under the scroll bar. In contrast, a `Class.ScrollingFrame.VerticalScrollBarInset|VerticalScrollBarInset` setting of either `Enum.ScrollBarInset|Always` or `Enum.ScrollBarInset|ScrollBar` insets the canvas by the `Class.ScrollingFrame.ScrollBarThickness|ScrollBarThickness` amount so that the canvas meets edge‑to‑edge with the scroll bar.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="None (Default)">
|
||||
<img src="../assets/ui/ui-objects/ScrollingFrame-Canvas-No-Inset.png" width="500" height="310" />
|
||||
</TabItem>
|
||||
<TabItem label="Always / ScrollBar">
|
||||
<img src="../assets/ui/ui-objects/ScrollingFrame-Canvas-Inset.png" width="500" height="310" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Scroll bars
|
||||
|
||||
The frame's **scroll bars** reflect the current canvas position and provide automatic drag‑to‑scroll interactivity. For vertical scroll bars, the `Class.ScrollingFrame.VerticalScrollBarPosition|VerticalScrollBarPosition` property switches the bar's position to either the left or right of the canvas, but horizontal scroll bars can only be positioned below the canvas.
|
||||
|
||||
Scroll bar construction consists of three asset properties:
|
||||
|
||||
- `Class.ScrollingFrame.TopImage|TopImage` — Displays on the top of a vertical scroll bar, or the left of a horizontal scroll bar (rotated 90° counterclockwise for a horizontal scroll bar).
|
||||
- `Class.ScrollingFrame.MidImage|MidImage` — Spans the area between `Class.ScrollingFrame.TopImage|TopImage` and `Class.ScrollingFrame.BottomImage|BottomImage` (rotated 90° counterclockwise for a horizontal scroll bar).
|
||||
- `Class.ScrollingFrame.BottomImage|BottomImage` — Displays on the bottom of a vertical scroll bar, or the right of a horizontal scroll bar (rotated 90° counterclockwise for a horizontal scroll bar).
|
||||
|
||||
<img src="../assets/ui/ui-objects/ScrollingFrame-Scroll-Bar-Elements.png" width="600" alt="Diagram showing the three image asset elements which construct a scrolling frame's scroll bar." />
|
||||
|
||||
Each image scales based on the `Class.ScrollingFrame.ScrollBarThickness|ScrollBarThickness` property which changes the width of a vertical scroll bar or the height of a horizontal scroll bar.
|
||||
|
||||
Additional visual modifications can be made through the `Class.ScrollingFrame.ScrollBarImageColor3|ScrollBarImageColor3` and `Class.ScrollingFrame.ScrollBarImageTransparency|ScrollBarImageTransparency` properties which tint the bar's image assets and modify its opacity, respectively.
|
||||
|
||||
## Elasticity
|
||||
|
||||
By default, `Class.ScrollingFrame|ScrollingFrames` exhibit elastic scrolling behavior on touch‑enabled devices, meaning that users can drag the canvas slightly further than its bounds and, upon release, the canvas will spring back to its minimum or maximum limit. If you want to remove the elastic behavior completely and ensure the canvas never scrolls past its limits, set the `Class.ScrollingFrame.ElasticBehavior|ElasticBehavior` property to `Enum.ElasticBehavior|Never`.
|
||||
112
.cursor/rules/roblox/en-us/ui/size-modifiers.md
Normal file
112
.cursor/rules/roblox/en-us/ui/size-modifiers.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
title: Size modifiers and constraints
|
||||
description: Explains how use size modifiers and size constraints with user interface objects.
|
||||
---
|
||||
|
||||
Alongside basic [sizing](../ui/position-and-size.md) of UI objects, you can utilize size modifiers to [scale](#scale) an object proportionally or [automatically resize](#automatic-sizing) it. You can also insert [size constraints](#constraints) to control **aspect ratio**, or set a minimum and maximum **size** or **text size**.
|
||||
|
||||
## Scale
|
||||
|
||||
A `Class.UIScale` object stores a numerical value that multiplies the `Class.GuiBase2d.AbsoluteSize|AbsoluteSize` property of the parent `Class.GuiObject`. For example, if you want an object to be twice as large as it currently is, you can insert a `Class.UIScale` object with a `Class.UIScale.Scale|Scale` property of `2`.
|
||||
|
||||
This modifier is useful for "zooming in" while designing a detailed user interface in Studio, since it proportionally scales the object and all of its children, including any applied [appearance modifiers](../ui/appearance-modifiers.md) like `Class.UIStroke` or `Class.UICorner`. It's also useful to [tween](../ui/animation.md) the size of an object, for example to slightly increase the size of a button when a player hovers their mouse over it.
|
||||
|
||||
<img src="../assets/ui/ui-objects/UIScale-Example.png" width="800" />
|
||||
|
||||
## Automatic sizing
|
||||
|
||||
The `Class.GuiObject.AutomaticSize|AutomaticSize` property automatically resizes a parent `Class.GuiObject` to the size of its descendants. You can use this property in a variety of cases, including:
|
||||
|
||||
- Expanding a `Class.GuiObject` to fit text that has been [localized](../production/localization/index.md) in many languages.
|
||||
- Allowing users to [input text](../ui/text-input.md) within a `Class.TextBox`, automatically adjusting its size based on the amount of text entered.
|
||||
- Automatically adjusting the size of text objects using [rich text markup](../ui/rich-text.md), including font type and size.
|
||||
|
||||
<video controls width="90%" src="../assets/ui/automatic-sizing/Intro-Frame.mp4"></video>
|
||||
|
||||
You can enable the `Class.GuiObject.AutomaticSize|AutomaticSize` property for any `Class.GuiObject`. By default, it is set to `Enum.AutomaticSize|None`, but you can change its value to have specific control over which direction(s) the `Class.GuiObject` resizes to fit its content.
|
||||
|
||||
<Alert severity="info">
|
||||
`Class.ScrollingFrame` uses the `Class.ScrollingFrame.AutomaticCanvasSize|AutomaticCanvasSize` property instead of `Class.GuiObject.AutomaticSize|AutomaticSize`. It behaves the same but resizes the frame's `Class.ScrollingFrame.CanvasSize|CanvasSize`, not its base `Class.GuiObject.Size|Size`.
|
||||
</Alert>
|
||||
|
||||
To enable automatic sizing:
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, click any `Class.GuiObject`, such as a `Class.Frame`, `Class.TextBox`, or `Class.ImageLabel`.
|
||||
2. In the [Properties](../studio/properties.md) window, navigate to the `Class.GuiObject.AutomaticSize|AutomaticSize` property (`Class.ScrollingFrame.AutomaticCanvasSize|AutomaticCanvasSize` for `Class.ScrollingFrame`) and set it to one of the following options:
|
||||
|
||||
- `Enum.AutomaticSize|X` — Resizes content horizontally.
|
||||
- `Enum.AutomaticSize|Y` — Resizes content vertically.
|
||||
- `Enum.AutomaticSize|XY` — Resizes content both horizontally and vertically.
|
||||
|
||||
<img src="../assets/studio/properties/GuiObject-AutomaticSize.png" width="320" />
|
||||
|
||||
Once automatic sizing is set, note that other object properties behave as follows:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="AnchorPoint">
|
||||
Similar to the concept of [sizing](../ui/position-and-size.md#size) an object, `Class.GuiObject.AutomaticSize|AutomaticSize` takes the `Class.GuiObject.AnchorPoint|AnchorPoint` property into consideration when resizing content. For example, the following video displays three `Class.TextLabel|TextLabels` that automatically resize from their anchor point.
|
||||
|
||||
<video controls width="80%" src="../assets/ui/automatic-sizing/customizing-anchorpoint.mp4"></video>
|
||||
|
||||
- The top label has an `Class.GuiObject.AnchorPoint|AnchorPoint` value of `Datatype.Vector2.new()|(0, 0.5)` which anchors it to the middle of the left side.
|
||||
- The middle label has an `Class.GuiObject.AnchorPoint|AnchorPoint` value of `Datatype.Vector2.new()|(0.5, 0.5)` which anchors it to the center.
|
||||
- The bottom label has an `Class.GuiObject.AnchorPoint|AnchorPoint` value of `Datatype.Vector2.new()|(1, 0.5)` which anchors it to the middle of the right side.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Size">
|
||||
When you enable `Class.GuiObject.AutomaticSize|AutomaticSize`, the object's `Class.GuiObject.Size|Size` property controls its **minimum size**. This means that the `Class.GuiObject` cannot resize smaller than its core size along any axis.
|
||||
|
||||
For example, the following white `Class.Frame` has a `Class.GuiObject.Size|Size` of `0` for both axes, meaning it doesn't have a minimum width/height and can resize according to its child objects.
|
||||
|
||||
<video controls width="80%" src="../assets/ui/automatic-sizing/customizing-size-0.mp4"></video>
|
||||
|
||||
In comparison, the following white `Class.Frame` has a `Class.GuiObject.Size|Size.X` of `100`, so it cannot resize to a width less than 100 pixels.
|
||||
|
||||
<video controls width="80%" src="../assets/ui/automatic-sizing/customizing-size-100.mp4"></video>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="TextWrapped">
|
||||
When you enable **text wrapping** for a UI text object (`Class.TextLabel.TextWrapped|TextLabel`, `Class.TextButton.TextWrapped|TextButton`, or `Class.TextBox.TextWrapped|TextBox`) and set the `Class.GuiObject.AutomaticSize|AutomaticSize` property to either `Enum.AutomaticSize|Y` or `Enum.AutomaticSize|XY`, the label resizes to accommodate any text that wraps across multiple lines. For example, the following `Class.TextLabel` has `Class.TextLabel.TextWrapped|TextWrapped` enabled and an `Class.GuiObject.AutomaticSize|AutomaticSize` of `Enum.AutomaticSize|Y`, so when text overflows the width and wraps, the label expands vertically.
|
||||
|
||||
<video controls width="80%" src="../assets/ui/automatic-sizing/customizing-textwrapping.mp4"></video>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Constraints
|
||||
|
||||
There are three types of **constraints** you can use for a user interface object: [size](#size), [text size](#text-size), and [aspect ratio](#aspect-ratio). To use a constraint, you must set it as a **child** of the `Class.GuiObject` you want to constrain.
|
||||
|
||||
### Size
|
||||
|
||||
The `Class.UISizeConstraint` specifies a minimum and maximum size for a `Class.GuiObject`. This constraint ensures that the `Class.GuiObject` doesn't become too small or large on different screen sizes.
|
||||
|
||||
For example, if you set the `Class.UISizeConstraint.MinSize|MinSize` property to <Typography noWrap>`(200, 200)`</Typography> and the `Class.UISizeConstraint.MaxSize|MaxSize` property to <Typography noWrap>`(400, 400)`</Typography>, the `Class.GuiObject` cannot scale smaller than 200×200 pixels or larger than 400×400 pixels.
|
||||
|
||||
<Alert severity="warning">
|
||||
When a UI object is under control of both a layout structure such as `Class.UIListLayout` and a `Class.UISizeConstraint`, the constraint will **override** the layout and control the object's size.
|
||||
</Alert>
|
||||
|
||||
### Text size
|
||||
|
||||
The `Class.UITextSizeConstraint` specifies a minimum and maximum font size for a `Class.GuiObject` with text, such as a `Class.TextLabel`, `Class.TextButton`, or `Class.TextBox`. This constraint ensures that the **text** within a `Class.GuiObject` doesn't become illegible or too large.
|
||||
|
||||
If you enable the `Class.TextLabel.TextScaled|TextScaled` property of the parent `Class.GuiObject`, the text size scales with the container's size and respects constraints even if the object becomes smaller or larger than the `Class.UITextSizeConstraint.MinTextSize|MinTextSize` and `Class.UITextSizeConstraint.MaxTextSize|MaxTextSize` values.
|
||||
|
||||
For example, the following `Class.TextLabel` object has a `Class.UITextSizeConstraint` with a `Class.UITextSizeConstraint.MinTextSize|MinTextSize` value of `50` and a MaxTextSize value of `80`. Even when the `Class.TextLabel` becomes smaller, the font never becomes smaller than 50 pixels, and when the object becomes large, the font next exceeds 80 pixels.
|
||||
|
||||
<video src="../assets/ui/ui-objects/UITextSizeConstraint.mp4" controls width="80%" ></video>
|
||||
|
||||
<Alert severity="error">
|
||||
Do not use `Class.UITextSizeConstraint.MinTextSize|MinTextSize` property values lower than `9` or the text will be difficult to read for many viewers.
|
||||
</Alert>
|
||||
|
||||
### Aspect ratio
|
||||
|
||||
The `Class.UIAspectRatioConstraint` enforces a **width‑to‑height** aspect ratio on a `Class.GuiObject` regardless of its core size, even if that size is set as a percentage of its parent. For example, inserting this constraint as a child of a `Class.Frame` and setting the constraint's `Class.UIAspectRatioConstraint.AspectRatio|AspectRatio` property to `2` (`2:1`) keeps the frame's width at twice that of its height. Similarly, setting this constraint's `Class.UIAspectRatioConstraint.AspectRatio|AspectRatio` property to `0.5` (`0.5:1`) keeps the frame's width at half that of its height.
|
||||
|
||||
Setting this constraint's `Class.UIAspectRatioConstraint.AspectRatio|AspectRatio` to the default of `1` (`1:1`) is a convenient way to prevent non‑proportional scaling/stretching of an `Class.ImageLabel` with a square image asset, such as an [avatar thumbnail](../projects/assets/index.md#rbxthumb).
|
||||
|
||||
<Alert severity="warning">
|
||||
When a UI object is under control of both a layout structure such as `Class.UIListLayout` and a `Class.UIAspectRatioConstraint`, the constraint will **override** the layout and control the object's size.
|
||||
</Alert>
|
||||
1012
.cursor/rules/roblox/en-us/ui/styling/compatibility.md
Normal file
1012
.cursor/rules/roblox/en-us/ui/styling/compatibility.md
Normal file
File diff suppressed because it is too large
Load diff
593
.cursor/rules/roblox/en-us/ui/styling/css-comparisons.md
Normal file
593
.cursor/rules/roblox/en-us/ui/styling/css-comparisons.md
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
---
|
||||
title: CSS Comparisons
|
||||
description: Explore how CSS concepts map to Roblox UI styling concepts.
|
||||
---
|
||||
|
||||
Most CSS concepts map to Roblox styling concepts. The following examples show how CSS and HTML align with Luau and Roblox classes/properties.
|
||||
|
||||
To test each of the following Luau script examples:
|
||||
|
||||
1. In the [Explorer](../../studio/explorer.md), create the following:
|
||||
|
||||
<img src="../../assets/studio/explorer/CSS-Test-Setup.png" width="320" style={{float: "right;", marginLeft: "20px;"}} />
|
||||
|
||||
1. `Class.StyleSheet` instance named `CoreSheet` inside `Class.ReplicatedStorage`.
|
||||
2. Empty `Class.StyleRule` instance as a child of `CoreSheet`.
|
||||
3. `Class.ScreenGui` container in `Class.StarterGui`.
|
||||
4. `Class.LocalScript` instance inside the `Class.ScreenGui`.
|
||||
5. `Class.StyleLink` object inside the `Class.ScreenGui` whose `Class.StyleLink.StyleSheet|StyleSheet` property is linked to `CoreSheet`.
|
||||
|
||||
2. In the `Class.LocalScript`, paste the following supporting code:
|
||||
|
||||
```lua title="LocalScript"
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local coreSheet = ReplicatedStorage:FindFirstChild("CoreSheet")
|
||||
local rule = coreSheet:FindFirstChildWhichIsA("StyleRule")
|
||||
local screenGui = script.Parent
|
||||
```
|
||||
|
||||
3. For each example below, paste the **Luau** code lines following the supporting lines 1–6.
|
||||
|
||||
## Selectors
|
||||
|
||||
The `Class.StyleRule.Selector|Selector` property of a `Class.StyleRule` specifies which instances the rule should affect. The following selector types map from CSS to Luau and can be used with combinators.
|
||||
|
||||
### Element
|
||||
|
||||
Equivalent to CSS element selectors are Roblox **class selectors** which select all instances of a given `Class.GuiObject` class, for example `Class.Frame`, `Class.ImageLabel`, `Class.TextButton`, etc.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
button {
|
||||
background-color: #335FFF;
|
||||
color: #E1E1E1;
|
||||
width: 15%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<button>Main Menu</button>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Class selector
|
||||
rule.Selector = "TextButton"
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("335FFF"),
|
||||
["TextColor3"] = Color3.fromHex("E1E1E1"),
|
||||
["Size"] = UDim2.new(0.15, 0, 0, 40),
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
local button = Instance.new("TextButton")
|
||||
button.Text = "Main Menu"
|
||||
button.Parent = screenGui
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
### Class
|
||||
|
||||
The Roblox equivalent to CSS `class` selectors are **tag selectors** which utilize [tags](../../studio/properties.md#instance-tags) applied through `Class.CollectionService`.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
.button-primary {
|
||||
background-color: #335FFF;
|
||||
color: #E1E1E1;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<button class="button-primary">Main Menu</button>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Tag selector
|
||||
rule.Selector = ".ButtonPrimary"
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("335FFF"),
|
||||
["TextColor3"] = Color3.fromHex("E1E1E1"),
|
||||
["AutomaticSize"] = Enum.AutomaticSize.XY
|
||||
})
|
||||
|
||||
local button = Instance.new("TextButton")
|
||||
button.Text = "Main Menu"
|
||||
button.Parent = screenGui
|
||||
-- Apply tag to button
|
||||
CollectionService:AddTag(button, "ButtonPrimary")
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
### Identifier
|
||||
|
||||
The closest Roblox comparison to CSS `id` is the `#[name]` selector which selects according to the value of `Class.Instance.Name`. Unlike the W3C specification of the `id` attribute, names are not expected to be unique.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
#modal-frame {
|
||||
background-color: #000022;
|
||||
opacity: 0.5;
|
||||
width: 50%;
|
||||
min-height: 100px;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<div id="modal-frame"></div>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Instance name selector
|
||||
rule.Selector = "#ModalFrame"
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("000022"),
|
||||
["BackgroundTransparency"] = 0.5,
|
||||
["Size"] = UDim2.new(0.5, 0, 0, 100),
|
||||
["AutomaticSize"] = Enum.AutomaticSize.Y
|
||||
})
|
||||
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Parent = screenGui
|
||||
-- Rename frame to match selector
|
||||
frame.Name = "ModalFrame"
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
## Combinators
|
||||
|
||||
Combinators let you mix basic [selectors](#selectors) to match deeper hierarchy relationships.
|
||||
|
||||
### Child
|
||||
|
||||
The child selector of `>` is identical between CSS and Roblox.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
.menu-container {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.menu-container > button {
|
||||
background-color: #335FFF;
|
||||
color: #E1E1E1;
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<div class="menu-container">
|
||||
<button>Options</button>
|
||||
</div>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Child selector
|
||||
rule.Selector = ".MenuContainer > TextButton"
|
||||
|
||||
-- Set rule properties for child
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("335FFF"),
|
||||
["TextColor3"] = Color3.fromHex("E1E1E1"),
|
||||
["Size"] = UDim2.new(0.8, 0, 0, 40),
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
-- Create menu container
|
||||
local menuContainer = Instance.new("Frame")
|
||||
menuContainer.Size = UDim2.new(0.25, 0, 0, 0)
|
||||
menuContainer.AutomaticSize = Enum.AutomaticSize.Y
|
||||
menuContainer.Parent = screenGui
|
||||
-- Apply tag
|
||||
CollectionService:AddTag(menuContainer, "MenuContainer")
|
||||
|
||||
-- Create button
|
||||
local button = Instance.new("TextButton")
|
||||
button.Text = "Options"
|
||||
-- Set menu container as parent of button
|
||||
button.Parent = menuContainer
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
### Descendant
|
||||
|
||||
Unlike the CSS whitespace syntax, for example <Typography noWrap>`.menu-container button`</Typography> seen here, Roblox uses the `>>` combinator to indicate a descendant relationship.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
.menu-container {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.sub-container {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.menu-container button {
|
||||
background-color: #335FFF;
|
||||
color: #E1E1E1;
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<div class="menu-container">
|
||||
<div class="sub-container">
|
||||
<button>Options</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Descendant selector
|
||||
rule.Selector = ".MenuContainer >> TextButton"
|
||||
|
||||
-- Set rule properties for descendant
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("335FFF"),
|
||||
["TextColor3"] = Color3.fromHex("E1E1E1"),
|
||||
["Size"] = UDim2.new(0.8, 0, 0, 40),
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
-- Create menu container
|
||||
local menuContainer = Instance.new("Frame")
|
||||
menuContainer.Size = UDim2.new(0.25, 0, 0, 0)
|
||||
menuContainer.AutomaticSize = Enum.AutomaticSize.Y
|
||||
menuContainer.Parent = screenGui
|
||||
-- Apply tag
|
||||
CollectionService:AddTag(menuContainer, "MenuContainer")
|
||||
|
||||
-- Create sub-container
|
||||
local subContainer = Instance.new("Frame")
|
||||
subContainer.Size = UDim2.new(0.75, 0, 0, 0)
|
||||
subContainer.AutomaticSize = Enum.AutomaticSize.Y
|
||||
-- Set menu container as parent of sub-container
|
||||
subContainer.Parent = menuContainer
|
||||
|
||||
-- Create button
|
||||
local button = Instance.new("TextButton")
|
||||
button.Text = "Options"
|
||||
-- Set sub-container as parent of button
|
||||
button.Parent = subContainer
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
### Selector list
|
||||
|
||||
Multiple selectors (including selectors with combinators) can be declared with the same property block, separated by commas, to reduce redundancy.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
img, p {
|
||||
background-color: #FF0033;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<img src="gear.png" width="100" height="100">
|
||||
|
||||
<p>Main Menu</p>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Selector for image labels AND text labels
|
||||
rule.Selector = "ImageLabel, TextLabel"
|
||||
|
||||
-- Set common property for classes
|
||||
rule:SetProperty("BackgroundColor3", Color3.fromHex("ff0033"))
|
||||
|
||||
-- Create image label
|
||||
local imageLabel = Instance.new("ImageLabel")
|
||||
imageLabel.Image = "rbxassetid://104919049969988"
|
||||
imageLabel.Size = UDim2.new(0, 100, 0, 100)
|
||||
imageLabel.Parent = screenGui
|
||||
|
||||
-- Create text label
|
||||
local textLabel = Instance.new("TextLabel")
|
||||
textLabel.Size = UDim2.new(1, 0, 0, 0)
|
||||
textLabel.AutomaticSize = Enum.AutomaticSize.Y
|
||||
textLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
textLabel.TextYAlignment = Enum.TextYAlignment.Top
|
||||
textLabel.Text = "Main Menu"
|
||||
textLabel.Parent = screenGui
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
## Pseudo-classes
|
||||
|
||||
The Roblox equivalent to CSS [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) selectors are **state selectors** which correspond to one of the four `Enum.GuiState` values such as `Enum.GuiState|Hover` or `Enum.GuiState|Press`.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
img:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<img src="gear.png" width="100" height="100">
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- State selector
|
||||
rule.Selector = "ImageLabel:Hover"
|
||||
|
||||
-- Set rule property
|
||||
rule:SetProperty("ImageTransparency", 0.5)
|
||||
|
||||
-- Create image label
|
||||
local label = Instance.new("ImageLabel")
|
||||
label.Image = "rbxassetid://104919049969988"
|
||||
label.Size = UDim2.new(0, 100, 0, 100)
|
||||
label.BackgroundTransparency = 1
|
||||
label.Parent = screenGui
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
## Pseudo-instances
|
||||
|
||||
Similar to how CSS [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements) can modify specific parts of an element, Roblox can create phantom `Class.UIComponent|UIComponents` through a style rule's `Class.StyleRule.Selector|Selector` property. For example, the following rule effectively creates a `Class.UICorner` modifier under every `Class.Frame` tagged with `RoundedCorner20` and sets each modifier's `Class.UICorner.CornerRadius|CornerRadius` to `20` pixels.
|
||||
|
||||
```lua title="Luau"
|
||||
-- UI component selector
|
||||
rule.Selector = "Frame.RoundedCorner20::UICorner"
|
||||
|
||||
-- Set rule property
|
||||
rule:SetProperty("CornerRadius", UDim.new(0, 20))
|
||||
|
||||
-- Create frame
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Size = UDim2.new(0.4, 0, 0.2, 0)
|
||||
frame.Parent = screenGui
|
||||
-- Apply tag to frame
|
||||
CollectionService:AddTag(frame, "RoundedCorner20")
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
CSS lets you declare and reference [variables](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) throughout a style system. Roblox achieves this through tokens and the [instance attributes](../../studio/properties.md#instance-attributes) system. Using `$` as a prefix, you can reference attributes declared in a `Class.StyleRule` or `Class.StyleSheet` inheritance chain when setting style properties.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="CSS"
|
||||
:root {
|
||||
--button-bg-color: #335FFF;
|
||||
--button-text-color: #E1E1E1;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--button-bg-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<button>Main Menu</button>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Set style sheet tokens using attributes
|
||||
coreSheet:SetAttribute("ButtonBgColor", Color3.fromHex("335FFF"))
|
||||
coreSheet:SetAttribute("ButtonTextColor", Color3.fromHex("E1E1E1"))
|
||||
|
||||
-- Class selector
|
||||
rule.Selector = "TextButton"
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = "$ButtonBgColor",
|
||||
["TextColor3"] = "$ButtonTextColor"
|
||||
})
|
||||
|
||||
-- Create button
|
||||
local button = Instance.new("TextButton")
|
||||
button.AutomaticSize = Enum.AutomaticSize.XY
|
||||
button.Text = "Main Menu"
|
||||
button.Parent = screenGui
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
## Nesting and merging
|
||||
|
||||
Borrowing a concept from [SCSS](https://sass-lang.com/guide/#nesting), `Class.StyleRule|StyleRules` can be nested together and their selectors will **merge**.
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
<GridContainer numColumns="2">
|
||||
<figure>
|
||||
```text title="SCSS"
|
||||
#menu-frame {
|
||||
background-color: #000022;
|
||||
width: 25%;
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
|
||||
> button {
|
||||
background-color: #335FFF;
|
||||
color: #E1E1E1;
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</figure>
|
||||
<figure>
|
||||
```text title="HTML"
|
||||
<div id="menu-frame">
|
||||
<button>Charms</button>
|
||||
<button>Mana</button>
|
||||
<button>Scrolls</button>
|
||||
</div>
|
||||
```
|
||||
</figure>
|
||||
</GridContainer>
|
||||
</CardContent>
|
||||
</Card><br />
|
||||
|
||||
<Card>
|
||||
<CardContent style={{paddingTop: '0px', paddingBottom: '0px'}}>
|
||||
```lua title="Luau"
|
||||
-- Menu frame rule
|
||||
local menuFrameRule = Instance.new("StyleRule")
|
||||
menuFrameRule.Parent = coreSheet
|
||||
menuFrameRule.Selector = "#MenuFrame"
|
||||
menuFrameRule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("000022"),
|
||||
["Size"] = UDim2.new(0.25, 0, 0, 200),
|
||||
["AutomaticSize"] = Enum.AutomaticSize.Y
|
||||
})
|
||||
|
||||
-- Menu layout rule
|
||||
local menuLayoutRule = Instance.new("StyleRule")
|
||||
menuLayoutRule.Parent = menuFrameRule -- Set menu frame rule as parent
|
||||
menuLayoutRule.Selector = "::UIListLayout"
|
||||
menuLayoutRule:SetProperties({
|
||||
["FillDirection"] = Enum.FillDirection.Vertical,
|
||||
["VerticalFlex"] = Enum.UIFlexAlignment.SpaceEvenly,
|
||||
["HorizontalAlignment"] = Enum.HorizontalAlignment.Center
|
||||
})
|
||||
|
||||
-- Button rule
|
||||
local buttonRule = Instance.new("StyleRule")
|
||||
buttonRule.Parent = menuFrameRule -- Set menu frame rule as parent
|
||||
buttonRule.Selector = "> TextButton"
|
||||
buttonRule:SetProperties({
|
||||
["BackgroundColor3"] = Color3.fromHex("335FFF"),
|
||||
["TextColor3"] = Color3.fromHex("E1E1E1"),
|
||||
["Size"] = UDim2.new(0.8, 0, 0, 40),
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
-- Button hover rule
|
||||
local buttonHoverRule = Instance.new("StyleRule")
|
||||
buttonHoverRule.Parent = buttonRule -- Set button rule as parent
|
||||
buttonHoverRule.Selector = ":hover"
|
||||
buttonHoverRule:SetProperty("BackgroundTransparency", 0.5)
|
||||
|
||||
-- Create parent frame
|
||||
local menuFrame = Instance.new("Frame")
|
||||
menuFrame.Parent = screenGui
|
||||
menuFrame.Name = "MenuFrame"
|
||||
|
||||
-- Create buttons inside frame
|
||||
local button1 = Instance.new("TextButton")
|
||||
button1.Text = "Charms"
|
||||
button1.Parent = menuFrame
|
||||
local button2 = Instance.new("TextButton")
|
||||
button2.Text = "Mana"
|
||||
button2.Parent = menuFrame
|
||||
local button3 = Instance.new("TextButton")
|
||||
button3.Text = "Scrolls"
|
||||
button3.Parent = menuFrame
|
||||
```
|
||||
</CardContent>
|
||||
</Card>
|
||||
458
.cursor/rules/roblox/en-us/ui/styling/editor.md
Normal file
458
.cursor/rules/roblox/en-us/ui/styling/editor.md
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
---
|
||||
title: Style Editor
|
||||
description: Explore the built-in Style Editor, a comprehensive tool that allows you to create, manage, and apply UI styles for Roblox experiences.
|
||||
---
|
||||
|
||||
The built-in **Style Editor** is a comprehensive tool that allows you to create, manage, and apply UI styles for Roblox experiences through a combination of [tokens](#style-tokens), [design sheets](#design-sheets), [style rules](#style-rules), and [themes](#style-themes).
|
||||
|
||||
<figure>
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/_k1ea0OIKaU?si=A9mdnnG95qdaZbB_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
||||
</figure>
|
||||
|
||||
Access the **Style Editor** via the **UI** tab.
|
||||
|
||||
<img src="../../assets/studio/general/Toolbar-Style-Editor.png" width="800" alt="Style Editor tool indicated in UI tab of Studio toolbar" />
|
||||
|
||||
Once open, click the **Create Design** button to generate a base style set.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Create-Design-Entry.png" width="350" alt="Create Design button in opening dialog of Style Editor." />
|
||||
|
||||
<Alert severity="warning">
|
||||
Creating a design style set through the Style Editor generates a **BaseStyleSheet** inside a **Design** folder within `Class.ReplicatedStorage`. This base sheet syncs specific engine‑level defaults like the `Datatype.Color3` default of <Typography noWrap>`[163, 162, 165]`</Typography> with defaults that come from insertion workflows in Studio. It's highly recommended that you do **not** delete or attempt to modify the base style sheet — instead, build tokens and style rules around a design sheet to match your UI goals.
|
||||
|
||||
Additionally, a **StyleSheet** sheet is generated in the **Design** folder which contains a `Class.StyleDerive` to the base style sheet. If you choose to remove that `Class.StyleDerive`, unstyled values for `Class.GuiObject|GuiObjects` may become unexpected values, for example a default `Class.GuiObject.Size|Size` of <Typography noWrap>`0, 0, 0, 0`</Typography>.
|
||||
</Alert>
|
||||
|
||||
## Style tokens
|
||||
|
||||
Style **tokens**, defined through [attributes](../../studio/properties.md#instance-attributes) of a token `Class.StyleSheet`, represent UI property variables that can be used across styles and components; for example, there could be a common color for a `Class.Frame.BackgroundColor3`, `Class.TextLabel.TextColor3`, and `Class.UIStroke.Color`. Tokens are comparable to [CSS variables](./css-comparisons.md#variables).
|
||||
|
||||
<Alert severity="success">
|
||||
Although not required for UI styling, tokens are highly recommended for extensible UI design and style updates over time. You should begin by creating the following tokens to understand how they tie into other aspects of universal styling.
|
||||
</Alert>
|
||||
|
||||
1. Create a new token style sheet:
|
||||
|
||||
1. In the left column of the Style Editor, hover over **Tokens**, click the **⊕**, and select **New Token StyleSheet**.
|
||||
2. Rename the new sheet to `TokenSheet`.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-TokenSheet-New.png" width="640" alt="New token sheet created in Style Editor." />
|
||||
|
||||
2. With the new token sheet selected, create several tokens by clicking **Add a Token…** in the main panel. These tokens will be used throughout this guide for both [rules](#style-rules) and [themes](#style-themes).
|
||||
|
||||
<table size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token Name</th>
|
||||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Gold`</td>
|
||||
<td>`Datatype.Color3`</td>
|
||||
<td>`#ffcc00`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Magenta`</td>
|
||||
<td>`Datatype.Color3`</td>
|
||||
<td>`#ff0099`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Orange`</td>
|
||||
<td>`Datatype.Color3`</td>
|
||||
<td>`#dd6030`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Oswald`</td>
|
||||
<td>`Datatype.Font`</td>
|
||||
<td>`Oswald`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Rad20`</td>
|
||||
<td>`Datatype.UDim`</td>
|
||||
<td>`0, 20`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`RectL`</td>
|
||||
<td>`Datatype.UDim2`</td>
|
||||
<td><Typography noWrap>`0, 300, 0, 160`</Typography></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`SquareL`</td>
|
||||
<td>`Datatype.UDim2`</td>
|
||||
<td>`0, 200, 0, 200`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Text24`</td>
|
||||
<td>number</td>
|
||||
<td>`24`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Text32`</td>
|
||||
<td>number</td>
|
||||
<td>`32`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-TokenSheet-Tokens.png" width="400" alt="Tokens added to TokenSheet in Style Editor." />
|
||||
|
||||
## Design sheets
|
||||
|
||||
A design style sheet aggregates [style rules](#style-rules) and can be linked to `Class.DataModel` trees to apply style properties to UI instances. Only one `Class.StyleSheet` can apply to a given tree, although you can use [themes](#style-themes) to swap related styles across your UI, a concept covered later in this guide.
|
||||
|
||||
A prepopulated design sheet named **StyleSheet** is created via the **Create Design** button. It contains class rules for common UI objects such as `Class.Frame` and `Class.TextLabel`. It also contains two `Class.StyleDerive` instances which derive (inherit) [tokens](#style-tokens) and styles from the base style sheet for use in your custom styling configurations.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Initial-Config.png" width="640" alt="Initial configuration of design sheet in Style Editor." />
|
||||
|
||||
Once you have a design sheet, you can set up a test [on‑screen container](../on-screen-containers.md) to use with the Style Editor, or an [in‑experience container](../in-experience-containers.md) if desired.
|
||||
|
||||
1. Hover over `Class.StarterGui` in the **Explorer** and insert a `Class.ScreenGui`.
|
||||
2. Confirm that a new `Class.StyleLink` instance appears under the `Class.ScreenGui` with its `Class.StyleLink.StyleSheet|StyleSheet` property set to the **StyleSheet** design sheet.
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/explorer/StarterGui-ScreenGui-StyleLink.png" width="320" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/properties/StyleLink-StyleSheet.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Alert severity="success">
|
||||
If the `Class.StyleLink` is not created, you can quickly create one by selecting the `Class.ScreenGui` in the **Explorer** as well as the **StyleSheet** design sheet in the left column of the Style Editor. Then, click the **Insert StyleLink** button in the upper‑right section of the editor's main panel.
|
||||
</Alert>
|
||||
|
||||
## Style rules
|
||||
|
||||
Style **rules** apply to every instance that matches the rule's `Class.StyleRule.Selector|Selector` definition to match characteristics such as class name, instance tag, and hierarchy relationships. At a high level, instance matching and modification via a rule's `Class.StyleRule.Selector|Selector` definition operates through:
|
||||
|
||||
- Roblox [class](#class-rule) selectors which target all instances of a given UI class, for example `Class.Frame`, `Class.ImageLabel`, `Class.TextButton`, etc.
|
||||
- Instance [tag](#tag-rule) selectors that target specific UI objects [tagged](../../studio/properties.md#instance-tags) through `Class.CollectionService`.
|
||||
- Instance [modifiers](#ui-modifier) applied through phantom `Class.UIComponent|UIComponents` such as `Class.UICorner` or `Class.UIStroke`.
|
||||
- `Class.GuiObject` [state](#state-rule) selectors which correspond to one of the four `Enum.GuiState` enum values such as `Enum.GuiState.Hover|Hover`.
|
||||
- Instance name selection according to the value of the UI object's `Class.Instance.Name`.
|
||||
|
||||
### Class rule
|
||||
|
||||
A style **class** selector targets all instances of a given UI class. The following rule setup styles all `Class.Frame|Frames` with a uniform background color and size.
|
||||
|
||||
1. In the left column of the Style Editor, select **Frame** in the **UI Elements** branch.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Class-Rule-New-Class.png" width="640" alt="Default class style in the Style Editor." />
|
||||
|
||||
2. Link two previously created [style tokens](#style-tokens) to two `Class.Frame` properties:
|
||||
|
||||
<table size="small" style={{width: '60%;'}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Style Token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.Frame.BackgroundColor3|BackgroundColor3`</td>
|
||||
<td>`$Magenta`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.Frame.Size|Size`</td>
|
||||
<td>`$SquareL`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Click **Add a Property…** in the main panel and select the necessary property. Note that you can type keywords to more quickly find properties in the dropdown menu.
|
||||
2. Instead of entering a static value, click the **⋮** button and select **Link Token**.
|
||||
3. Click the `$` which appears in the value field and select the proper associated token.
|
||||
|
||||
<figure>
|
||||
<br /><img src="../../assets/ui/ui-styling/SE-Class-Rule-Configure.png" width="526" alt="Token linking workflow diagrammed in the Style Editor." />
|
||||
</figure>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Class-Rule-Tokens.png" width="400" alt="Class rule configured with tokens in the Style Editor." />
|
||||
|
||||
3. Insert a new `Class.Frame` into the `Class.ScreenGui` you previously [created and linked](#design-sheets). The styles you defined should automatically apply to it.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Class-Rule-Result.png" width="840" alt="Final styled class rule in the Style Editor." />
|
||||
|
||||
### Tag rule
|
||||
|
||||
Instance **tag** selectors target specific UI objects [tagged](../../studio/properties.md#instance-tags) through `Class.CollectionService`. The following rule setup styles a `Class.TextButton` tagged as `ButtonPrimary` with a custom background color, font, and text size.
|
||||
|
||||
1. In the left column of the Style Editor, hover over the **StyleSheet** sheet, click the **⋮** button, and navigate through to **New** ⟩ **Tag**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Tag-Rule-New.png" width="600" alt="Creation of tag rule in the Style Editor." />
|
||||
|
||||
2. Rename the new tag rule `.ButtonPrimary` (note the leading `.`).
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Tag-Rule-Renamed.png" width="240" alt="Tag rule renamed in the Style Editor." />
|
||||
|
||||
3. Link three previously created [style tokens](#style-tokens) to three `Class.TextButton` properties:
|
||||
|
||||
<table size="small" style={{width: '60%;'}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Style Token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.TextButton.BackgroundColor3|BackgroundColor3`</td>
|
||||
<td>`$Gold`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton.FontFace|FontFace`</td>
|
||||
<td>`$Oswald`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton.TextSize|TextSize`</td>
|
||||
<td>`$Text32`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Click **Add a Property…** in the main panel and select the necessary property. Remember that you can type keywords to more quickly find properties in the dropdown menu.
|
||||
2. Instead of entering a static value, click the **⋮** button and select **Link Token**.
|
||||
3. Click the `$` which appears in the value field and select the proper associated token.
|
||||
|
||||
<figure>
|
||||
<br /><img src="../../assets/ui/ui-styling/SE-Tag-Rule-Configure.png" width="526" alt="Token linking workflow diagrammed in the Style Editor." />
|
||||
</figure>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Tag-Rule-Tokens.png" width="400" alt="Tag rule configured with tokens in the Style Editor." />
|
||||
|
||||
4. Insert a new `Class.TextButton` into the `Class.ScreenGui` you previously [created and linked](#design-sheets) and [tag](../../studio/properties.md#instance-tags) it as `ButtonPrimary`. A convenient shortcut is as follows:
|
||||
|
||||
1. Make sure the new `Class.TextButton` is selected in the **Explorer**.
|
||||
2. With the **.ButtonPrimary** tag rule selected in the left column of the Style Editor, click **Apply Tag** in the main panel. The styles you defined should automatically apply to the button.
|
||||
|
||||
<figure>
|
||||
<br /><img src="../../assets/ui/ui-styling/SE-Tag-Rule-Apply-Tag.png" width="640" alt="Tag linking workflow diagrammed in the Style Editor." />
|
||||
</figure>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Tag-Rule-Result.png" width="840" alt="Final styled tag rule in the Style Editor." />
|
||||
|
||||
### UI modifier
|
||||
|
||||
Instance modifier selectors apply phantom `Class.UIComponent|UIComponents` such as `Class.UICorner` or `Class.UIStroke` to further style an object. The following rule setup styles a `Class.TextLabel` with a custom text size and rounded corners.
|
||||
|
||||
1. In the left column of the Style Editor, select **TextLabel** in the **UI Elements** branch.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-New-Class.png" width="640" alt="Default class style in the Style Editor." />
|
||||
|
||||
2. Link a previously created [style token](#style-tokens) to the `Class.TextLabel.TextSize|TextSize` property:
|
||||
|
||||
1. Click **Add a Property…** in the main panel and select the `Class.TextLabel.TextSize|TextSize` property. Remember that you can type keywords to more quickly find properties in the dropdown menu.
|
||||
2. Instead of entering a static value, click the **⋮** button and select **Link Token**.
|
||||
3. Click the `$` which appears in the value field and select the `$Text32` token.
|
||||
|
||||
<figure>
|
||||
<br /><img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Configure.png" width="526" alt="Token linking workflow diagrammed in the Style Editor." />
|
||||
</figure>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Tokens.png" width="400" alt="Class rule configured with tokens in the Style Editor." />
|
||||
|
||||
A **pseudo instance** is required to configure and apply rounded corners to other elements. To create one:
|
||||
|
||||
1. In the left column, hover over **TextLabel**, click the **⋮** button, and navigate through to **New** ⟩ **Pseudo Instance** ⟩ **UICorner**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Pseudo-Instance-New.png" width="760" alt="Creation of a pseudo instance in the Style Editor." />
|
||||
|
||||
A new **UICorner** pseudo instance appears under the **TextLabel** element in the left column.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Pseudo-Instance-Created.png" width="240" alt="Resulting pseudo instance in the Style Editor." />
|
||||
|
||||
2. With the new **UICorner** instance selected in the left column, link a previously created [style token](#style-tokens) to the `Class.UICorner.CornerRadius|CornerRadius` property:
|
||||
|
||||
1. Click **Add a Property…** in the main panel and select the `Class.UICorner.CornerRadius|CornerRadius` property.
|
||||
2. Instead of entering a static value, click the **⋮** button and select **Link Token**.
|
||||
3. Click the `$` which appears in the value field and select the `$Rad20` token.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Pseudo-Instance-Tokens.png" width="400" alt="Pseudo instance configured with tokens in the Style Editor." />
|
||||
|
||||
3. Insert a new `Class.TextLabel` into the `Class.ScreenGui` you previously [created and linked](#design-sheets). The styles you defined should automatically apply to it.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Modifier-Rule-Result.png" width="840" alt="Final styled modifier rule in the Style Editor." />
|
||||
|
||||
### State rule
|
||||
|
||||
`Class.GuiObject` **state** selectors correspond to one of the four `Enum.GuiState|GuiState` enum values such as `Enum.GuiState|Hover`, letting you automatically configure style changes for interactive states. The following rule setup creates a hover state of `-4` degrees rotation for all `Class.ImageButton|ImageButtons`.
|
||||
|
||||
1. In the left column of the Style Editor, select **ImageButton** in the **UI Elements** branch.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-State-Rule-New-Class.png" width="640" alt="Default class style in the Style Editor." />
|
||||
|
||||
2. Click the **⋮** button and navigate through to **New** ⟩ **GuiState** ⟩ **Hover**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-State-Rule-New.png" width="760" alt="Creation of state rule in the Style Editor." />
|
||||
|
||||
A new **Hover** state modifier instance appears under the **ImageButton** element in the left column.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-State-Rule-Created.png" width="240" alt="Resulting state rule in the Style Editor." />
|
||||
|
||||
3. With the new **Hover** modifier selected in the left column, click **Add a Property…** in the main panel and select `Class.ImageButton.Rotation|Rotation`. Remember that you can type keywords to more quickly find properties in the dropdown menu.
|
||||
4. Enter `-4` in the property's value field.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-State-Rule-Tokens.png" width="400" alt="State rule configured with tokens in the Style Editor." />
|
||||
|
||||
5. Insert a new `Class.ImageButton` into the `Class.ScreenGui` you previously [created and linked](#design-sheets). When you hover over the button in the viewport, it should rotate 4 degrees counterclockwise.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-State-Rule-Result.png" width="840" alt="Final styled state rule in the Style Editor." />
|
||||
|
||||
## Style themes
|
||||
|
||||
Style **themes** consist of sets of specific tokens that can be swapped, for example color tokens that define a "light" and "dark" theme.
|
||||
|
||||
### Theme creation
|
||||
|
||||
For extensibility, themes are organized into folders. While a single folder may suffice for most purposes, you're free to organize themes in folders like "colors" or "fonts" if desired.
|
||||
|
||||
1. Create a new theme folder:
|
||||
|
||||
1. In the left column of the Style Editor, hover over **Themes**, click the **⊕**, and select **New Theme**.
|
||||
2. Rename the new **Folder** item to **General**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-New-Folder.png" width="240" alt="New themes folder created in the Style Editor." />
|
||||
|
||||
2. Create a new theme style sheet:
|
||||
|
||||
1. Hover over the new **General** folder, click the **⋮** button, and select **New Theme StyleSheet**.
|
||||
2. Rename it to **ThemeA**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-New-Theme.png" width="240" alt="New theme created in the Style Editor." />
|
||||
|
||||
### Theme tokens
|
||||
|
||||
Once a theme is constructed, you can link its tokens to various UI object properties such as the `Class.TextButton.BackgroundColor3|BackgroundColor3` of a `Class.TextButton`. Theme sheets must use a common set of tokens to work correctly.
|
||||
|
||||
With **ThemeA** selected in the left column of the Style Editor:
|
||||
|
||||
1. Link three previously created [style tokens](#style-tokens) to three new theme tokens:
|
||||
|
||||
<table size="small" style={{width: '60%;'}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Theme Token</th>
|
||||
<th>Style Token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`BackColor`</td>
|
||||
<td>`$Gold`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`ButtonFont`</td>
|
||||
<td>`$Oswald`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`ButtonTextSize`</td>
|
||||
<td>`$Text32`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Click **Add a Token…** in the main panel and enter the theme token's name.
|
||||
2. Click the `$` which appears in the value field and select the associated style token.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-ThemeA-Tokens.png" width="400" alt="Theme configured with tokens in the Style Editor." />
|
||||
|
||||
2. In the left column of the Style Editor, select **TextButton** in the **UI Elements** branch.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-New-Class.png" width="640" alt="Default class style in the Style Editor." />
|
||||
|
||||
3. Link the theme's tokens to three new `Class.TextButton` properties:
|
||||
|
||||
<table size="small" style={{width: '60%;'}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Theme Token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.TextButton.BackgroundColor3|BackgroundColor3`</td>
|
||||
<td>`$BackColor`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton.FontFace|FontFace`</td>
|
||||
<td>`$ButtonFont`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.TextButton.TextSize|TextSize`</td>
|
||||
<td>`$ButtonTextSize`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. Click **Add a Property…** in the main panel and select the necessary property.
|
||||
2. Instead of entering a static value, click the **⋮** button and select **Link Token**.
|
||||
3. Click the `$` which appears in the value field and select the proper theme token.
|
||||
|
||||
<figure>
|
||||
<br /><img src="../../assets/ui/ui-styling/SE-Themes-Theme-Configure.png" width="526" alt="Token linking workflow diagrammed in the Style Editor." />
|
||||
<Alert severity="warning">
|
||||
When linking the theme's tokens, look for **\$\[Token\] in ThemeA** in the hover‑over to know the token is coming from the **theme** and not from the global token sheet (remember that the theme already derives the global tokens).
|
||||
</Alert>
|
||||
</figure>
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-Class-Tokens.png" width="400" alt="Class rule configured with theme tokens in the Style Editor." />
|
||||
|
||||
### Theme duplication
|
||||
|
||||
Once you have a theme generally established, you can **duplicate** it and change various tokens to define a unique theme style.
|
||||
|
||||
1. In the left column of the Style Editor, hover over the **ThemeA** theme, click the **⋮** button, and select **Duplicate**.
|
||||
2. Rename the duplicated theme to **ThemeB**.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-Duplicated-Theme.png" width="240" alt="Theme duplicated in the Style Editor." />
|
||||
|
||||
3. Link two of the theme's tokens to two different style tokens:
|
||||
|
||||
<table size="small" style={{width: '60%;'}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Theme Token</th>
|
||||
<th>Style Token</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`BackColor`</td>
|
||||
<td>`$Magenta`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`ButtonTextSize`</td>
|
||||
<td>`$Text24`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
1. To the right of the property's value field, click the **⋮** button and select **Unlink Token**.
|
||||
2. Click **⋮** again and select **Link Token**.
|
||||
3. In its value field, click the `$` and select the new associated style token.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-ThemeB-Tokens.png" width="400" alt="Theme configured with tokens in the Style Editor." />
|
||||
|
||||
### Theme swapping
|
||||
|
||||
Once you have [multiple themes](#theme-duplication), you can swap between them via the theme's folder, or via a script as outlined in `Class.StyleSheet:SetDerives()|SetDerives()`.
|
||||
|
||||
1. In the left column of the Style Editor, select the **General** folder in the **Themes** branch.
|
||||
2. In the main panel, swap between the themes using the radio buttons.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="ThemeA">
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-Swapping-ThemeA.png" width="640" height="216" alt="ThemeA swapped in the Style Editor." />
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-ThemeA-Result.png" width="800" height="403" />
|
||||
</TabItem>
|
||||
<TabItem label="ThemeB">
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-Swapping-ThemeB.png" width="640" height="216" alt="ThemeB swapped in the Style Editor." />
|
||||
<img src="../../assets/ui/ui-styling/SE-Themes-ThemeB-Result.png" width="800" height="403" />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
322
.cursor/rules/roblox/en-us/ui/styling/index.md
Normal file
322
.cursor/rules/roblox/en-us/ui/styling/index.md
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
---
|
||||
title: UI styling
|
||||
description: Explore how stylesheets let you declare and globally apply overrides to UI instance properties.
|
||||
---
|
||||
|
||||
UI styling is a Roblox solution to stylesheets, [similar to CSS](css-comparisons.md), that lets you declare and globally apply overrides to UI instance properties. This engine‑level support is the foundation for the [Style Editor](./editor.md) and the end‑to‑end token pipeline.
|
||||
|
||||
## Concepts
|
||||
|
||||
Style **rules** (part of a `Class.StyleSheet`) apply to every instance that matches the rule's `Class.StyleRule.Selector|Selector` definition to match characteristics such as class name, instance name, and hierarchy relationships. See the `Class.StyleRule.Selector|Selector` documentation for details.
|
||||
|
||||
Style **tokens**, defined through [attributes](../../studio/properties.md#instance-attributes) of a token `Class.StyleSheet`, represent UI property variables that can be used across styles and components, for example a common color for a `Class.Frame.BackgroundColor3`, `Class.TextLabel.TextColor3`, and `Class.UIStroke.Color`. Tokens are comparable to CSS [variables](css-comparisons.md#variables).
|
||||
|
||||
Style **themes**, configured through [attributes](../../studio/properties.md#instance-attributes) of a theme `Class.StyleSheet`, consist of sets of specific tokens that can be swapped, for example color tokens that define a "light" and "dark" theme. Related themes must have the same set of tokens to work correctly.
|
||||
|
||||
<figure>
|
||||
<img src="../../assets/studio/explorer/Styling-Hierarchy.png" width="414" />
|
||||
</figure>
|
||||
|
||||
## Rule propagation
|
||||
|
||||
A `Class.StyleLink` instance links a `Class.StyleSheet` and its associated rules to a parent `Class.ScreenGui` and all of the `Class.GuiObject|GuiObjects` within it. Only one `Class.StyleSheet` can apply to a given tree.
|
||||
|
||||
<img src="../../assets/studio/explorer/StyleLink-Propagation.png" width="320" />
|
||||
|
||||
## Selector definitions
|
||||
|
||||
At a high level, instance matching and modification via a rule's `Class.StyleRule.Selector|Selector` definition operates through:
|
||||
|
||||
- Roblox **class** selectors which target all instances of a given `Class.GuiObject` class, for example `Class.Frame`, `Class.ImageLabel`, `Class.TextButton`, etc.
|
||||
- Instance [tags](../../studio/properties.md#instance-tags) applied to specific UI objects through `Class.CollectionService`.
|
||||
- Instance **name** selection according to the value of the UI object's `Class.Instance.Name`.
|
||||
- Instance **modifiers**, similar to [CSS pseudo-elements](css-comparisons.md#pseudo-instances), applied through phantom `Class.UIComponent|UIComponents` such as `Class.UICorner` or `Class.UIStroke`.
|
||||
- `Class.GuiObject` **state** selectors, similar to [CSS pseudo-class](css-comparisons.md#pseudo-classes) selectors, which correspond to one of the four `Enum.GuiState|GuiState` enum values such as `Enum.GuiState|Hover`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Class Selector">
|
||||
The following setup shows how size and color tokens, a theme, and a class selector of `"Frame"` produce a magenta `Class.Frame` of size 200×200 pixels.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/Styling-Flowchart-Class.png" width="840" />
|
||||
<BaseAccordion>
|
||||
<AccordionSummary><Typography variant="subtitle2">Code Equivalent</Typography></AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
```lua title="UI Class Selector"
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local screenGui = script.Parent
|
||||
|
||||
-- Tokens
|
||||
local tokens = Instance.new("StyleSheet")
|
||||
tokens.Name = "Tokens"
|
||||
tokens.Parent = ReplicatedStorage
|
||||
tokens:SetAttribute("SquareS", UDim2.fromOffset(50, 50))
|
||||
tokens:SetAttribute("SquareM", UDim2.fromOffset(100, 100))
|
||||
tokens:SetAttribute("SquareL", UDim2.fromOffset(200, 200))
|
||||
tokens:SetAttribute("Fit", UDim2.fromScale(1, 1))
|
||||
tokens:SetAttribute("Magenta", Color3.fromHex("FF0099"))
|
||||
tokens:SetAttribute("Gold", Color3.fromHex("FFCC00"))
|
||||
tokens:SetAttribute("Aqua", Color3.fromHex("0093F0"))
|
||||
|
||||
-- ThemeA
|
||||
local themeA = Instance.new("StyleSheet")
|
||||
themeA.Name = "ThemeA"
|
||||
themeA.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeA
|
||||
themeA:SetAttribute("FrameSize", "$SquareM")
|
||||
themeA:SetAttribute("FrameColor", "$Aqua")
|
||||
|
||||
-- ThemeB
|
||||
local themeB = Instance.new("StyleSheet")
|
||||
themeB.Name = "ThemeB"
|
||||
themeB.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeB
|
||||
themeB:SetAttribute("FrameSize", "$SquareL")
|
||||
themeB:SetAttribute("FrameColor", "$Magenta")
|
||||
|
||||
-- DesignSheet
|
||||
local designSheet = Instance.new("StyleSheet")
|
||||
designSheet.Name = "DesignSheet"
|
||||
designSheet.Parent = ReplicatedStorage
|
||||
local themeDerive = Instance.new("StyleDerive")
|
||||
themeDerive.StyleSheet = themeB -- Derive from ThemeB
|
||||
themeDerive.Parent = designSheet
|
||||
|
||||
-- Link DesignSheet to ScreenGui
|
||||
local styleLink = Instance.new("StyleLink")
|
||||
styleLink.StyleSheet = designSheet
|
||||
styleLink.Parent = screenGui
|
||||
|
||||
-- Configure rule
|
||||
local rule = Instance.new("StyleRule")
|
||||
rule.Selector = "Frame" -- Class selector
|
||||
rule.Parent = designSheet
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = "$FrameColor",
|
||||
["Size"] = "$FrameSize",
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Parent = screenGui
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Tag Selector">
|
||||
This setup shows how size/color/font tokens, a theme, and a tag selector of `".ButtonPrimary"` produces a gold button using the `Enum.Font|Oswald` font at `32` pixels.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/Styling-Flowchart-Tag.png" width="840" />
|
||||
<BaseAccordion>
|
||||
<AccordionSummary><Typography variant="subtitle2">Code Equivalent</Typography></AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
```lua title="UI Tag Selector"
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local screenGui = script.Parent
|
||||
|
||||
-- Tokens
|
||||
local tokens = Instance.new("StyleSheet")
|
||||
tokens.Name = "Tokens"
|
||||
tokens.Parent = ReplicatedStorage
|
||||
tokens:SetAttribute("Text24", 24)
|
||||
tokens:SetAttribute("Text32", 32)
|
||||
tokens:SetAttribute("Magenta", Color3.fromHex("FF0099"))
|
||||
tokens:SetAttribute("Gold", Color3.fromHex("FFCC00"))
|
||||
tokens:SetAttribute("Aqua", Color3.fromHex("0093F0"))
|
||||
tokens:SetAttribute("DenkOne", Enum.Font.DenkOne)
|
||||
tokens:SetAttribute("Oswald", Enum.Font.Oswald)
|
||||
|
||||
-- ThemeA
|
||||
local themeA = Instance.new("StyleSheet")
|
||||
themeA.Name = "ThemeA"
|
||||
themeA.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeA
|
||||
themeA:SetAttribute("BackColor", "$Gold")
|
||||
themeA:SetAttribute("TextSize", "$Text32")
|
||||
themeA:SetAttribute("ButtonFont", "$Oswald")
|
||||
|
||||
-- ThemeB
|
||||
local themeB = Instance.new("StyleSheet")
|
||||
themeB.Name = "ThemeB"
|
||||
themeB.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeB
|
||||
themeB:SetAttribute("BackColor", "$Aqua")
|
||||
themeB:SetAttribute("TextSize", "$Text24")
|
||||
themeB:SetAttribute("ButtonFont", "$DenkOne")
|
||||
|
||||
-- DesignSheet
|
||||
local designSheet = Instance.new("StyleSheet")
|
||||
designSheet.Name = "DesignSheet"
|
||||
designSheet.Parent = ReplicatedStorage
|
||||
local themeDerive = Instance.new("StyleDerive")
|
||||
themeDerive.StyleSheet = themeA -- Derive from ThemeA
|
||||
themeDerive.Parent = designSheet
|
||||
|
||||
-- Link DesignSheet to ScreenGui
|
||||
local styleLink = Instance.new("StyleLink")
|
||||
styleLink.StyleSheet = designSheet
|
||||
styleLink.Parent = screenGui
|
||||
|
||||
-- Configure rule
|
||||
local rule = Instance.new("StyleRule")
|
||||
rule.Selector = ".ButtonPrimary" -- Tag selector
|
||||
rule.Parent = designSheet
|
||||
|
||||
-- Set rule properties
|
||||
rule:SetProperties({
|
||||
["BackgroundColor3"] = "$BackColor",
|
||||
["TextSize"] = "$TextSize",
|
||||
["Font"] = "$ButtonFont",
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
|
||||
local menuButton = Instance.new("TextButton")
|
||||
menuButton.Size = UDim2.fromOffset(200, 44)
|
||||
menuButton.Text = "Menu"
|
||||
menuButton.Parent = screenGui
|
||||
CollectionService:AddTag(menuButton, "ButtonPrimary") -- Apply tag to button
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="UI Modifier Selector">
|
||||
This setup shows how tokens, a theme, a class selector of `"Frame"`, and a nested UI modifier selector of `"::UICorner"` produce a 300×160 orange `Class.Frame` with a `Class.UICorner` modifier of `20` pixels.
|
||||
|
||||
<img src="../../assets/ui/ui-styling/Styling-Flowchart-Modifier.png" width="840" />
|
||||
<BaseAccordion>
|
||||
<AccordionSummary><Typography variant="subtitle2">Code Equivalent</Typography></AccordionSummary>
|
||||
<AccordionDetails>
|
||||
|
||||
```lua title="UI Modifier Selector"
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local screenGui = script.Parent
|
||||
|
||||
-- Tokens
|
||||
local tokens = Instance.new("StyleSheet")
|
||||
tokens.Name = "Tokens"
|
||||
tokens.Parent = ReplicatedStorage
|
||||
tokens:SetAttribute("RectS", UDim2.fromOffset(75, 40))
|
||||
tokens:SetAttribute("RectM", UDim2.fromOffset(150, 80))
|
||||
tokens:SetAttribute("RectL", UDim2.fromOffset(300, 160))
|
||||
tokens:SetAttribute("Magenta", Color3.fromHex("FF0099"))
|
||||
tokens:SetAttribute("Orange", Color3.fromHex("DD6030"))
|
||||
tokens:SetAttribute("Radius20", UDim.new(0, 20))
|
||||
tokens:SetAttribute("RadiusPill", UDim.new(0.5, 0))
|
||||
|
||||
-- ThemeA
|
||||
local themeA = Instance.new("StyleSheet")
|
||||
themeA.Name = "ThemeA"
|
||||
themeA.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeA
|
||||
themeA:SetAttribute("FrameSize", "$RectM")
|
||||
themeA:SetAttribute("FrameColor", "$Magenta")
|
||||
themeA:SetAttribute("CornerRad", "$Radius20")
|
||||
|
||||
-- ThemeB
|
||||
local themeB = Instance.new("StyleSheet")
|
||||
themeB.Name = "ThemeB"
|
||||
themeB.Parent = ReplicatedStorage
|
||||
local tokensDerive = Instance.new("StyleDerive")
|
||||
tokensDerive.StyleSheet = tokens -- Derive global tokens
|
||||
tokensDerive.Parent = themeB
|
||||
themeB:SetAttribute("FrameSize", "$RectL")
|
||||
themeB:SetAttribute("FrameColor", "$Orange")
|
||||
themeB:SetAttribute("CornerRad", "$Radius20")
|
||||
|
||||
-- DesignSheet
|
||||
local designSheet = Instance.new("StyleSheet")
|
||||
designSheet.Name = "DesignSheet"
|
||||
designSheet.Parent = ReplicatedStorage
|
||||
local themeDerive = Instance.new("StyleDerive")
|
||||
themeDerive.StyleSheet = themeB -- Derive from ThemeB
|
||||
themeDerive.Parent = designSheet
|
||||
|
||||
-- Link DesignSheet to ScreenGui
|
||||
local styleLink = Instance.new("StyleLink")
|
||||
styleLink.StyleSheet = designSheet
|
||||
styleLink.Parent = screenGui
|
||||
|
||||
-- Configure rules
|
||||
local frameRule = Instance.new("StyleRule")
|
||||
frameRule.Selector = "Frame" -- Class selector
|
||||
frameRule.Parent = designSheet
|
||||
local modifierRule = Instance.new("StyleRule")
|
||||
modifierRule.Selector = "::UICorner" -- UI modifier selector
|
||||
modifierRule.Parent = frameRule
|
||||
|
||||
-- Set rule properties
|
||||
frameRule:SetProperties({
|
||||
["BackgroundColor3"] = "$FrameColor",
|
||||
["Size"] = "$FrameSize",
|
||||
["BorderSizePixel"] = 0
|
||||
})
|
||||
modifierRule:SetProperty("CornerRadius", "$CornerRad")
|
||||
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Parent = screenGui
|
||||
```
|
||||
|
||||
</AccordionDetails>
|
||||
</BaseAccordion>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Modified properties
|
||||
|
||||
Instance properties affected by styling are flagged with a **⚠** in the **Properties** window, and hovering over an affected property shows which style is influencing it. For example, if a style rule tells a tagged `Class.Frame` to use an `Class.Frame.AnchorPoint|AnchorPoint` of <Typography noWrap>`0.5, 0.5`</Typography>, that object property will show the **default** value of <Typography noWrap>`0, 0`</Typography> but its actual `Class.Frame.AnchorPoint|AnchorPoint` will use the rule's **styled** value.
|
||||
|
||||
<img src="../../assets/studio/properties/Styling-Modified-Property.png" width="320" />
|
||||
|
||||
When a default or styled property value is further modified for a specific UI object, it becomes **bold** to indicate an overridden value. For example — assuming a default `Class.Frame.AnchorPoint|AnchorPoint` of <Typography noWrap>`0, 0`</Typography> — setting that property to <Typography noWrap>`1, 1`</Typography> for a specific instance reveals an override in bold:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Default Property Value">
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/explorer/StarterGui-ScreenGui-HUDContainer.png" width="320" height="90" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/properties/AnchorPoint-Default.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Overridden Property Value">
|
||||
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/explorer/StarterGui-ScreenGui-MenuFrame.png" width="320" height="90" />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<img src="../../assets/studio/properties/AnchorPoint-Overridden.png" width="320" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
For any overridden property value, you can right-click it in the **Properties** window and select **Reset to Default** to revert it to the styled value, or to the default property value if it hasn't been styled.
|
||||
101
.cursor/rules/roblox/en-us/ui/text-filtering.md
Normal file
101
.cursor/rules/roblox/en-us/ui/text-filtering.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: Text filtering
|
||||
description: Text filtering prevents users from seeing inappropriate language and blocks personally identifiable information.
|
||||
---
|
||||
|
||||
Applied to various sources and inputs, **text filtering** prevents users from seeing inappropriate language and personally identifiable information such as phone numbers. Roblox automatically filters common text outputs such as messages that have passed through [in-experience text chat](../chat/in-experience-text-chat.md), but **you are responsible for filtering any displayed text that you don't have explicit control over**.
|
||||
|
||||
<Alert severity="error">
|
||||
Because filtering is crucial for a safe environment, Roblox actively moderates the content of experiences to make sure they meet [community standards](https://en.help.roblox.com/hc/en-us/articles/203313410-Roblox-Community-Standards). If Roblox receives reports or automatically detects that your experience doesn't apply text filtering, then the system removes the experience until you add filtering.
|
||||
</Alert>
|
||||
|
||||
## Filter scenarios
|
||||
|
||||
Text can be gathered and/or displayed to users in a variety of scenarios, including:
|
||||
|
||||
- An experience that gathers users' [text input](../ui/text-input.md) through `Class.TextBox` entries, a custom GUI with buttons such as a keyboard/keypad interface, or an interactive keyboard model in the 3D space.
|
||||
|
||||
- An experience that generates words from random characters and displays them to users, as there's a chance it will create inappropriate words.
|
||||
|
||||
- An experience that connects to an external web server to fetch content that is displayed in-experience. Often you will not have control over the content of the external site and a third party can edit the information.
|
||||
|
||||
- An experience that stores text such as users' pet names using [data stores](../cloud-services/data-stores), where the stored text might include inappropriate words that should be filtered when retrieving them.
|
||||
|
||||
## Filtering process
|
||||
|
||||
`Class.TextService:FilterStringAsync()` filters in-experience text by taking a string of text and the `Class.Player.UserId|UserId` of the user who created the text as input. It returns a `Class.TextFilterResult` object which has two additional methods that you can call in different scenarios:
|
||||
|
||||
- `Class.TextFilterResult:GetNonChatStringForBroadcastAsync()` for filtering text visible to all users in an experience, such as for a dialog that lets a user write a message on a sign that's visible to all users on the server.
|
||||
- `Class.TextFilterResult:GetNonChatStringForUserAsync()` to display filtered text for one specific user, based on age and other details.
|
||||
|
||||
In the context of `Class.TextBox` input, the following example gathers input on the `Class.TextBox.FocusLost|FocusLost` event and sends it to the server through a `Class.RemoteEvent`. On the server, it is filtered first through `Class.TextService:FilterStringAsync()|FilterStringAsync()` and then `Class.TextFilterResult:GetNonChatStringForBroadcastAsync()|GetNonChatStringForBroadcastAsync()` with the intention that the text will be displayed to all users on a server‑side object such as a `Class.SurfaceGui` in the 3D world.
|
||||
|
||||
<Alert severity="warning">
|
||||
Do not filter text in real time "per character entered" into a `Class.TextBox`, as doing so yields for text that's only visible to the user typing it. Instead, filter the entered text **after** the user submits it.
|
||||
</Alert>
|
||||
|
||||
```lua title="Filtering Text Input - Client Script"
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local textBox = script.Parent
|
||||
textBox.ClearTextOnFocus = false
|
||||
textBox.PlaceholderText = "..."
|
||||
textBox.TextXAlignment = Enum.TextXAlignment.Left
|
||||
textBox.TextScaled = true
|
||||
|
||||
-- RemoteEvent to send text input to server for filtering
|
||||
local inputRemoteEvent = ReplicatedStorage:FindFirstChild("InputRemoteEvent")
|
||||
|
||||
-- Event handler for focus lost and enter being pressed
|
||||
local function onFocusLost(enterPressed, inputObject)
|
||||
if enterPressed then
|
||||
print("SUBMITTED:", textBox.Text)
|
||||
if inputRemoteEvent then
|
||||
inputRemoteEvent:FireServer(textBox.Text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
textBox.FocusLost:Connect(onFocusLost)
|
||||
```
|
||||
|
||||
```lua title="Filtering Text Input - Server Script"
|
||||
local TextService = game:GetService("TextService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
-- RemoteEvent to receive text input from client for filtering
|
||||
local inputRemoteEvent = ReplicatedStorage:FindFirstChild("InputRemoteEvent")
|
||||
|
||||
local function getFilterResult(text, fromUserId)
|
||||
local filterResult
|
||||
local success, errorMessage = pcall(function()
|
||||
filterResult = TextService:FilterStringAsync(text, fromUserId)
|
||||
end)
|
||||
|
||||
if success then
|
||||
return filterResult
|
||||
else
|
||||
warn("Error generating TextFilterResult:", errorMessage)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fired when client submits input from the TextBox
|
||||
local function onInputReceived(player, text)
|
||||
if text ~= "" then
|
||||
local filterResult = getFilterResult(text, player.UserId)
|
||||
if filterResult then
|
||||
local success, filteredText = pcall(function()
|
||||
return filterResult:GetNonChatStringForBroadcastAsync()
|
||||
end)
|
||||
|
||||
if success then
|
||||
print("FILTERED:", filteredText)
|
||||
else
|
||||
warn("Error filtering text!")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
inputRemoteEvent.OnServerEvent:Connect(onInputReceived)
|
||||
```
|
||||
93
.cursor/rules/roblox/en-us/ui/text-input.md
Normal file
93
.cursor/rules/roblox/en-us/ui/text-input.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
title: Text input fields
|
||||
description: Text input fields allow users to input text from their physical or on-screen keyboard.
|
||||
---
|
||||
|
||||
import TextFiltering from '../includes/text-filtering/text-filtering.md'
|
||||
|
||||
A `Class.TextBox` is a rectangle that allows a user to provide text input while it's in focus. When you [script](#script-text-inputs) a `Class.TextBox`, you can use it as a search bar or an input field on a form. To help users know what type of text they should input, you can also provide a prompt through the `Class.TextBox.PlaceholderText|PlaceholderText` property.
|
||||
|
||||
<img src="../assets/ui/ui-objects/TextBox-Example.jpg" width="840" />
|
||||
|
||||
Because these objects are `Class.GuiObject|GuiObjects`, you can customize
|
||||
properties such as `Class.GuiObject.BackgroundColor3|BackgroundColor3`,
|
||||
`Class.GuiObject.BorderMode|BorderMode`,
|
||||
`Class.GuiObject.Transparency|Transparency`, and
|
||||
`Class.GuiObject.Rotation|Rotation` to fit the aesthetics of your experience.
|
||||
|
||||
## Create text inputs on the screen
|
||||
|
||||
A `Class.TextBox` on a screen is useful for
|
||||
things like an input field for a form.
|
||||
|
||||
To add a `Class.TextBox` to a screen:
|
||||
|
||||
1. In the **Explorer** window, select **StarterGui** and add a **ScreenGui**.
|
||||
|
||||
1. Hover over StarterGui and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **ScreenGui**.
|
||||
|
||||
2. Select the new **ScreenGui** and add a **TextBox**.
|
||||
|
||||
1. Hover over **ScreenGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **TextBox**.
|
||||
|
||||
## Create text inputs on part faces
|
||||
|
||||
To add a `Class.TextBox` to the face of a part:
|
||||
|
||||
1. In the **Explorer** window, select **StarterGui** and add a **SurfaceGui**.
|
||||
|
||||
1. Hover over StarterGui and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **ScreenGui**.
|
||||
|
||||
2. Select the new **SurfaceGui** and add a **TextBox**.
|
||||
|
||||
1. Hover over **SurfaceGui** and click the ⊕ button. A contextual menu displays.
|
||||
|
||||
1. Insert a **TextBox**.
|
||||
|
||||
3. Adorn the **SurfaceGui** to the **part** on which you want to display the **TextBox**.
|
||||
|
||||
1. In the **Properties** window, select the **Adornee** property. Your cursor changes.
|
||||
|
||||
1. In the **Explorer** window, select the part.
|
||||
|
||||
<Alert severity="warning">
|
||||
If you don't see the `Class.TextBox`, try [choosing a different face](../parts/textures-decals.md#choose-a-face) in the <b>Face</b> property of the <b>SurfaceGui</b>.
|
||||
</Alert>
|
||||
|
||||
## Script text inputs
|
||||
|
||||
Like [buttons](../ui/buttons.md), you can script any action for a `Class.TextBox` object when a user interacts with it. For example, the following script connects the `Class.TextBox.FocusLost|FocusLost` event which fires when the user presses the <kbd>Enter</kbd> button or clicks outside the box. If `enterPressed` is `true`, meaning the user submitted the input instead of merely clicking outside the box, the script prints the contents of the entry to the [Output](../studio/output.md) window.
|
||||
|
||||
```lua title="Basic Text Input Handling"
|
||||
local textBox = script.Parent
|
||||
|
||||
local function onFocusLost(enterPressed, inputObject)
|
||||
if enterPressed then
|
||||
print(textBox.Text)
|
||||
end
|
||||
end
|
||||
|
||||
textBox.FocusLost:Connect(onFocusLost)
|
||||
```
|
||||
|
||||
As another example, you might want to allow only numbers in a `Class.TextBox`. The following code uses the `Class.TextBox.GetPropertyChangedSignal` event to detect when the `Class.TextBox.Text` changes, such as when a user starts typing, then it uses the `Library.string.gsub()` function to disallow non‑numbers.
|
||||
|
||||
```lua title="Restricting Text Input to Numbers"
|
||||
local textBox = script.Parent
|
||||
|
||||
local function allowOnlyNumbers()
|
||||
textBox.Text = string.gsub(textBox.Text, "%D", "")
|
||||
end
|
||||
|
||||
textBox:GetPropertyChangedSignal("Text"):Connect(allowOnlyNumbers)
|
||||
```
|
||||
|
||||
## Text filtering
|
||||
|
||||
<TextFiltering components={props.components} context="characters/strings that users input through text inputs" />
|
||||
367
.cursor/rules/roblox/en-us/ui/ui-drag-detectors.md
Normal file
367
.cursor/rules/roblox/en-us/ui/ui-drag-detectors.md
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
---
|
||||
title: UI drag detectors
|
||||
description: UI drag detectors facilitate and encourage interaction with 2D user interface elements in an experience, such as sliders and spinners.
|
||||
---
|
||||
|
||||
The `Class.UIDragDetector` instance facilitates and encourages interaction with 2D user interface elements in an experience, such as sliders, spinners, and more. Key features include:
|
||||
|
||||
- Place a `Class.UIDragDetector` under any `Class.GuiObject` instance to make it draggable via all user inputs without a single line of code.
|
||||
|
||||
- Choose from several `Class.UIDragDetector.DragStyle|DragStyle` options, define how the object responds to motion via `Class.UIDragDetector.ResponseStyle|ResponseStyle`, and optionally apply axis, movement limits, or drag boundaries.
|
||||
|
||||
- Scripts can respond to manipulation of dragged objects to drive logic responses, such as adjusting settings.
|
||||
|
||||
- `Class.UIDragDetector|UIDragDetectors` work in Studio's edit and play mode as long as you're **not** using the **Select**, **Move**, **Scale**, or **Rotate** tools, nor certain plugins or Studio's **UI** editor tools.
|
||||
|
||||
<video src="../assets/ui/ui-drag-detectors/Showcase.mp4" controls width="100%" alt="How to add UI Drag Detectors and Drag in Edit Mode"></video>
|
||||
|
||||
<Alert severity="info">
|
||||
For drag detectors that manipulate 3D objects in an experience, such as opening doors and drawers or sliding a part around, see [3D Drag Detectors](../ui/3D-drag-detectors.md).
|
||||
</Alert>
|
||||
|
||||
## Make UI elements draggable
|
||||
|
||||
To make any `Class.GuiObject` instances draggable, simply add a `Class.UIDragDetector` as a direct descendant.
|
||||
|
||||
1. In the [Explorer](../studio/explorer.md) window, hover over the `Class.GuiObject` instance and click the ⊕ button. A contextual menu displays.
|
||||
1. From the menu, insert a **UIDragDetector**.
|
||||
|
||||
<img src="../assets/studio/explorer/StarterGui-UIDragDetector.png" width="320" />
|
||||
|
||||
1. By default, the object will now be draggable in the `Class.LayerCollector|LayerCollector` interface.
|
||||
|
||||
<Alert severity="warning">
|
||||
Remember that `Class.UIDragDetector|UIDragDetectors` only work in Studio if you're **not** using the **Select**, **Move**, **Scale**, or **Rotate** tools, nor certain plugins or Studio's **UI** editor tools.
|
||||
</Alert>
|
||||
|
||||
## Customize UI drag detectors
|
||||
|
||||
### Drag style
|
||||
|
||||
`Class.UIDragDetector|UIDragDetectors` map cursor motion to calculate proposed 2D motion and/or rotation. Through the `Class.UIDragDetector.DragStyle|DragStyle` property, you can choose from different mappings to suit your needs. For example, `Enum.UIDragDetectorDragStyle.TranslatePlane` produces translation in the 2D plane of the `Class.LayerCollector`, while `Enum.UIDragDetectorDragStyle.Rotate` normally produces a rotation instead of translation.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorDragStyle.TranslateLine|TranslateLine`</td>
|
||||
<td>1D motion along the detector's `Class.UIDragDetector.DragAxis|DragAxis`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorDragStyle.TranslatePlane|TranslatePlane`</td>
|
||||
<td>2D motion in the plane of the `Class.LayerCollector`.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorDragStyle.Rotate|Rotate`</td>
|
||||
<td>By default, rotation about the absolute center position of the detector's parent `Class.GuiObject`. If `Class.UIDragDetector.ReferenceUIInstance|ReferenceUIInstance` is set, rotation happens about that instance's absolute center position.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorDragStyle.Scriptable|Scriptable`</td>
|
||||
<td>Calculates desired motion via a custom function provided through `Class.UIDragDetector:SetDragStyleFunction()|SetDragStyleFunction()`.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Drag direction
|
||||
|
||||
By default, 2D motion and the associated `Class.UIDragDetector.DragStyle|DragStyle` map to the space of the ancestor `Class.LayerCollector`. However, you may want to change the `Class.UIDragDetector.ReferenceUIInstance|ReferenceUIInstance` or the `Class.UIDragDetector.DragAxis|DragAxis` when building different UI components.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.ReferenceUIInstance|ReferenceUIInstance`</td>
|
||||
<td>A `Class.GuiObject` instance whose local space and absolute center position is the reference space and origin for the detector. Setting this reference affects properties such as `Class.UIDragDetector.DragUDim2|DragUDim2`, `Class.UIDragDetector.DragRotation|DragRotation`, and the behavior of `Class.UIDragDetector.DragAxis|DragAxis`.</td>
|
||||
<td>`nil`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.DragAxis|DragAxis`</td>
|
||||
<td>`Datatype.Vector2` value that defines the axis of movement for the dragged object when `Class.UIDragDetector.DragStyle|DragStyle` is set to `Enum.UIDragDetectorDragStyle.TranslateLine`. The axis is defined in the local space of the `Class.UIDragDetector` unless `Class.UIDragDetector.ReferenceUIInstance|ReferenceUIInstance` is defined, in which case the axis is defined in that instance's local space.</td>
|
||||
<td>(1, 0)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Response to motion
|
||||
|
||||
The `Class.UIDragDetector.ResponseStyle` property specifies how an object's position value is changed by the proposed motion. The custom response styles let you use the resulting `Class.UIDragDetector.DragUDim2` and `Class.UIDragDetector.DragRotation` values as desired, without having the detector's parent execute the proposed motion.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorResponseStyle.Offset|Offset`</td>
|
||||
<td>Move by the `Datatype.UDim.Offset|Offset` values of the detector's parent's `Class.GuiObject.Position` value. This is the setting by default.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorResponseStyle.Scale|Scale`</td>
|
||||
<td>Move by the `Datatype.UDim.Scale|Scale` values of the detector's parent's `Class.GuiObject.Position` value.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorResponseStyle.CustomOffset|CustomOffset`</td>
|
||||
<td>The UI element will not move at all, but the `Datatype.UDim.Offset|Offset` values of the detector's `Class.UIDragDetector.DragUDim2|DragUDim2` will still be updated and the detector's events will still fire, allowing you to respond to drag manipulation however you'd like.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Enum.UIDragDetectorResponseStyle.CustomScale|CustomScale`</td>
|
||||
<td>The UI element will not move at all, but the `Datatype.UDim.Scale|Scale` values of the detector's `Class.UIDragDetector.DragUDim2|DragUDim2` will still be updated and the detector's events will still fire, allowing you to respond to drag manipulation however you'd like.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Translation & rotation limits
|
||||
|
||||
By default, there are no limits to 2D motion behind the inherent restrictions of the `Class.UIDragDetector.DragStyle|DragStyle`. Limits for both minimum and maximum translations and rotations can be declared with the following properties if desired. Additionally, you can define how the dragged object is constrained within the bounds of a specified `Class.GuiObject` such as a `Class.Frame`.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Properties</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.MinDragTranslation|MinDragTranslation`<br />
|
||||
`Class.UIDragDetector.MaxDragTranslation|MaxDragTranslation`</td>
|
||||
<td>Limits to drag translation in each dimension, defined by a `Datatype.UDim2` value. If `Class.UIDragDetector.MaxDragTranslation|MaxDragTranslation` is greater than `Class.UIDragDetector.MinDragTranslation|MinDragTranslation`, translation will be clamped within that range.</td>
|
||||
<td>\{0, 0\}, \{0, 0\}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.MinDragAngle|MinDragAngle`<br />
|
||||
`Class.UIDragDetector.MaxDragAngle|MaxDragAngle`</td>
|
||||
<td>Only relevant if `Class.UIDragDetector.DragStyle|DragStyle` is set to `Enum.UIDragDetectorDragStyle.Rotate`, or if the functions set through `Class.UIDragDetector:SetDragStyleFunction()|SetDragStyleFunction()` or `Class.UIDragDetector:AddConstraintFunction()|AddConstraintFunction()` defines a rotation value. If `Class.UIDragDetector.MaxDragAngle|MaxDragAngle` is greater than `Class.UIDragDetector.MinDragAngle|MinDragAngle`, rotation will be clamped within that range.</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.BoundingBehavior|BoundingBehavior`</td>
|
||||
<td>Determines the `Class.UIDragDetector` instance's bounding behavior when its `Class.UIDragDetector.BoundingUI|BoundingUI` is set. Setting this to `Enum.UIDragDetectorBoundingBehavior.EntireObject|EntireObject` bounds the entire dragged UI within the `Class.UIDragDetector.BoundingUI|BoundingUI`, while setting it to `Enum.UIDragDetectorBoundingBehavior.HitPoint|HitPoint` bounds the dragged UI only by the exact hit/grab point and its respective position after translation/rotation. As a convenience, the default of `Enum.UIDragDetectorBoundingBehavior.Automatic|Automatic` mimics the `Enum.UIDragDetectorBoundingBehavior.EntireObject|EntireObject` behavior for a UI object that's entirely contained by the `Class.UIDragDetector.BoundingUI|BoundingUI`, or else `Enum.UIDragDetectorBoundingBehavior.HitPoint|HitPoint` for a UI object that's partially outside the `Class.UIDragDetector.BoundingUI|BoundingUI`.</td>
|
||||
<td>`Enum.UIDragDetectorBoundingBehavior.Automatic|Automatic`</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Speed adjustments
|
||||
|
||||
Through `Class.UIDragDetector.SelectionModeDragSpeed|SelectionModeDragSpeed` and `Class.UIDragDetector.SelectionModeRotateSpeed|SelectionModeRotateSpeed`, you can fine‑tune the maximum drag/rotate speeds for a detector. Furthermore, through `Class.UIDragDetector.UIDragSpeedAxisMapping|UIDragSpeedAxisMapping`, you can fine‑tune the **X**/**Y** dimension dragging speeds, based on the detector's `Class.UIDragDetector.SelectionModeDragSpeed|SelectionModeDragSpeed`.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.SelectionModeDragSpeed|SelectionModeDragSpeed`</td>
|
||||
<td>Defines the maximum drag speed for translation as a combination of `Datatype.UDim.Scale|Scale` and `Datatype.UDim.Offset|Offset` of the first ancestor `Class.ScreenGui` or `Class.SurfaceGui` the `Class.UIDragDetector` belongs to.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.SelectionModeRotateSpeed|SelectionModeRotateSpeed`</td>
|
||||
<td>Defines the maximum angle per second at which the `Class.UIDragDetector` can rotate.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.UIDragSpeedAxisMapping|UIDragSpeedAxisMapping`</td>
|
||||
<td>Determines the **X**/**Y** dimension dragging speeds, based on the detector's `Class.UIDragDetector.SelectionModeDragSpeed|SelectionModeDragSpeed`. The default is `Enum.UIDragSpeedAxisMapping.XY|XY`, meaning the **X** and **Y** axis speeds are based off the **X** and **Y** `Datatype.UDim.Scale|Scale`/`Datatype.UDim.Offset|Offset` values respectively.<br /><br />Alternatives are `Enum.UIDragSpeedAxisMapping|XX` and `Enum.UIDragSpeedAxisMapping|YY`, meaning both the **X** and **Y** axis speeds are based off the **X** (`Enum.UIDragSpeedAxisMapping|XX`) or **Y** (`Enum.UIDragSpeedAxisMapping|YY`) axis for `Datatype.UDim.Scale|Scale`, while the `Datatype.UDim.Offset|Offset` values still apply to their respective axis. For example, if the first ancestor `Class.ScreenGui` is sized 800×600 and `Class.UIDragDetector.SelectionModeDragSpeed|SelectionModeDragSpeed` is <Typography noWrap>`{0.1, 10}, {0.1, 20}`</Typography>, a setting of `Enum.UIDragSpeedAxisMapping|XX` results in an **X**/**Y** drag speed of `80+10`/`80+20`, while `Enum.UIDragSpeedAxisMapping|YY` results in `60+10`/`60+20` (note the `Datatype.UDim.Offset|Offset` values remain the same in both cases).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Script responses to clicking and dragging
|
||||
|
||||
Through [event signals](#event-signals), property changes, `Enum.UIDragDetectorDragStyle.Scriptable|Scriptable` drag style, and custom functions, scripts can respond to the manipulation of dragged UI elements to drive various settings or make logical decisions, such as sliders that adjust music and sound effect volume separately.
|
||||
|
||||
<Alert severity="warning">
|
||||
For user-initiated script responses like [Event Signals](#event-signals) and [Scripted Drag Style](#scripted-drag-style), you'll most commonly need to put your script code inside a `Class.LocalScript`, or a `Class.Script` with `Class.BaseScript.RunContext|RunContext` set to `Enum.RunContext.Client|Client`.
|
||||
</Alert>
|
||||
|
||||
### Event signals
|
||||
|
||||
Through the following event signals, you can detect when a user starts, continues, and ends dragging an object.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.DragStart|DragStart`</td>
|
||||
<td>Fires when a user starts dragging the object.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.DragContinue|DragContinue`</td>
|
||||
<td>Fires when a user continues dragging the object after `Class.UIDragDetector.DragStart|DragStart` has been initiated.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.UIDragDetector.DragEnd|DragEnd`</td>
|
||||
<td>Fires when a user stops dragging the object.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
The following slider designates its container as the `Class.UIDragDetector.BoundingUI|BoundingUI` to limit its movement within the container area, allowing the scale‑based `Enum.UIDragDetectorDragStyle|TranslateLine` drag to be limited to the full width of the container without extra scripting.
|
||||
|
||||
<video src="../assets/ui/ui-drag-detectors/Transparency-Slider.mp4" controls width="90%" alt="Video demonstrating event signals utilized to implement a transparency slider widget."></video>
|
||||
|
||||
<Alert>
|
||||
To test this example, download the <a href="../assets/ui/ui-drag-detectors/TransparencySlider.rbxm">`TransparencySlider.rbxm`</a> file, right‑click `Class.StarterGui` in the [Explorer](../studio/explorer.md) window, select **Insert from File**, and choose the downloaded file.
|
||||
</Alert>
|
||||
|
||||
```lua title="UIDragDetector - Event Signal Transparency Change"
|
||||
-- Hierarchy is SliderContainer ⟩ Handle ⟩ UIDragDetector ⟩ (this script)
|
||||
local sliderContainer = script.Parent.Parent.Parent
|
||||
local handle = sliderContainer:FindFirstChild("Handle")
|
||||
local uiDragDetector = handle:FindFirstChildWhichIsA("UIDragDetector")
|
||||
|
||||
uiDragDetector.ResponseStyle = Enum.UIDragDetectorResponseStyle.Scale -- Set dragging by scale
|
||||
uiDragDetector.DragStyle = Enum.UIDragDetectorDragStyle.TranslateLine -- Restricts dragging to line
|
||||
uiDragDetector.BoundingUI = sliderContainer
|
||||
|
||||
-- Initially set container transparency to X scale value of handle
|
||||
sliderContainer.BackgroundTransparency = 1 - handle.Position.X.Scale
|
||||
|
||||
-- Expand handle border to indicate grab start
|
||||
uiDragDetector.DragStart:Connect(function(inputPosition)
|
||||
handle:FindFirstChildWhichIsA("UIStroke").Thickness = 6
|
||||
end)
|
||||
|
||||
-- Change transparency by how much it dragged in scale
|
||||
uiDragDetector.DragContinue:Connect(function(inputPosition)
|
||||
sliderContainer.BackgroundTransparency = 1 - handle.Position.X.Scale
|
||||
end)
|
||||
|
||||
-- Revert handle border to indicate grab end
|
||||
uiDragDetector.DragEnd:Connect(function(inputPosition)
|
||||
handle:FindFirstChildWhichIsA("UIStroke").Thickness = 4
|
||||
end)
|
||||
```
|
||||
|
||||
### Position & rotation changes
|
||||
|
||||
In addition to [event signals](#event-signals), you can monitor changes to the detector's `Class.UIDragDetector.DragUDim2|DragUDim2` and/or `Class.UIDragDetector.DragRotation|DragRotation` properties directly.
|
||||
|
||||
The following detector has its `Class.UIDragDetector.DragStyle|DragStyle` set to `Enum.UIDragDetectorDragStyle|Rotate`, allowing users to drag the handle around the hue rotator ring, all while detecting changes to drag rotation through `Class.Instance:GetPropertyChangedSignal()`.
|
||||
|
||||
<video src="../assets/ui/ui-drag-detectors/Hue-Rotator.mp4" controls width="90%" alt="Video demonstrating property change detection to implement a hue rotator widget."></video>
|
||||
|
||||
<Alert>
|
||||
To test this example, download the <a href="../assets/ui/ui-drag-detectors/HueRotator.rbxm">`HueRotator.rbxm`</a> file, right‑click `Class.StarterGui` in the [Explorer](../studio/explorer.md) window, select **Insert from File**, and choose the downloaded file.
|
||||
</Alert>
|
||||
|
||||
```lua title="UIDragDetector - DragRotation Change"
|
||||
local handle = script.Parent.Parent -- UI element to drag
|
||||
local uiDragDetector = handle:FindFirstChildWhichIsA("UIDragDetector")
|
||||
|
||||
uiDragDetector.DragStyle = Enum.UIDragDetectorDragStyle.Rotate -- Set drag style to rotate
|
||||
|
||||
local function changeHue()
|
||||
local currAngle = (math.fmod(handle.Rotation, 360)) / 360
|
||||
if currAngle < 0 then
|
||||
currAngle += 1
|
||||
end
|
||||
handle.BackgroundColor3 = Color3.fromHSV(currAngle, 1, 1)
|
||||
end
|
||||
|
||||
-- Initially set hue to handle rotation
|
||||
changeHue()
|
||||
|
||||
-- Connect function to GetPropertyChangedSignal() of the detector's drag rotation
|
||||
uiDragDetector:GetPropertyChangedSignal("DragRotation"):Connect(changeHue)
|
||||
```
|
||||
|
||||
### Scripted drag style
|
||||
|
||||
If you set a detector's `Class.UIDragDetector.DragStyle` to `Enum.UIDragDetectorDragStyle.Scriptable`, you can provide your own function that takes in a `Datatype.Vector2` of the input position and returns a `Datatype.UDim2` (position) and a float (rotation). The detector will update the object to the computed position/rotation based off of the returns, the `Class.UIDragDetector.DragSpace|DragSpace` property, and the `Class.UIDragDetector.DragRelativity|DragRelativity` property.
|
||||
|
||||
By default, the returned `Datatype.UDim2` and float will be the **final** desired position/rotation in the local space of the detector's parent. Existing translation/rotation limits will still apply, as will boundary limits imposed by a specified `Class.UIDragDetector.BoundingUI|BoundingUI` instance.
|
||||
|
||||
The following example drags a UI element following a sine wave computed by the change in **X** coordinate input. Note that the detector's `Class.UIDragDetector.DragSpace|DragSpace` is set to `Enum.UIDragDetectorDragSpace.Relative`.
|
||||
|
||||
```lua title="UIDragDetector - Drag Following Sine Wave"
|
||||
local frame = script.Parent -- UI element to drag
|
||||
local uiDragDetector = frame:FindFirstChildWhichIsA("UIDragDetector")
|
||||
|
||||
local initialXValue = 0
|
||||
local maxHeightChange = 200
|
||||
local pixelsPerRadian = 75 -- Lower this value to increase frequency
|
||||
|
||||
uiDragDetector.DragStart:Connect(function(inputPosition)
|
||||
initialXValue = inputPosition.X
|
||||
end)
|
||||
|
||||
local function computeSinWaveCoordinate(inputPosition)
|
||||
local deltaX = inputPosition.X - initialXValue
|
||||
-- Negative Y delta so that it goes "up" on the screen with positive Y change
|
||||
local deltaY = -math.sin(deltaX / pixelsPerRadian) * maxHeightChange
|
||||
return UDim2.fromOffset(deltaX, deltaY)
|
||||
end
|
||||
|
||||
uiDragDetector:SetDragStyleFunction(computeSinWaveCoordinate)
|
||||
```
|
||||
|
||||
### Custom constraint function
|
||||
|
||||
`Class.UIDragDetector|UIDragDetectors` do not have built-in motion rules about grids and snapping, but you can register custom constraint functions to edit the detector's `Class.UIDragDetector.DragUDim2` and `Class.UIDragDetector.DragRotation` before they are applied. For example, you can keep motion on a grid by rounding positions to specific increments, or define allowed areas of motion. Note that this is applied **before** any existing translation/rotation limits.
|
||||
|
||||
<Alert severity="info">
|
||||
Multiple constraint functions can be registered and they will be called in the order of their priority value passed in upon registration, from least to greatest.
|
||||
</Alert>
|
||||
|
||||
The following example utilizes a constraint function that clamps the planar drag into an **X**/**Y** grid based on the number of rows and columns. Note that the detector's `Class.UIDragDetector.ResponseStyle|ResponseStyle` is set to `Enum.UIDragDetectorResponseStyle.Scale` and its `Class.UIDragDetector.BoundingUI|BoundingUI` is set to the grid container.
|
||||
|
||||
<video src="../assets/ui/ui-drag-detectors/Grid-Drag.mp4" controls width="90%" alt="Video demonstrating a custom constraint function that clamps the planar drag into a grid."></video>
|
||||
|
||||
<Alert>
|
||||
To test this example, download the <a href="../assets/ui/ui-drag-detectors/GridDrag.rbxm">`GridDrag.rbxm`</a> file, right‑click `Class.StarterGui` in the [Explorer](../studio/explorer.md) window, select **Insert from File**, and choose the downloaded file.
|
||||
</Alert>
|
||||
|
||||
```lua title="UIDragDetector - Drag in Grid, Snapping to Tiles"
|
||||
-- Hierarchy is GridContainer ⟩ Handle ⟩ UIDragDetector ⟩ (this script)
|
||||
local gridContainer = script.Parent.Parent.Parent
|
||||
local handle = gridContainer:FindFirstChild("Handle") -- UI element to drag
|
||||
local uiDragDetector = handle:FindFirstChildWhichIsA("UIDragDetector")
|
||||
|
||||
uiDragDetector.ResponseStyle = Enum.UIDragDetectorResponseStyle.Scale -- Set dragging by scale
|
||||
uiDragDetector.DragRelativity = Enum.UIDragDetectorDragRelativity.Relative
|
||||
uiDragDetector.BoundingUI = gridContainer
|
||||
|
||||
local NUM_COLUMNS = 10
|
||||
local NUM_ROWS = 5
|
||||
|
||||
local xScaleIncrement = 1 / NUM_COLUMNS
|
||||
local yScaleIncrement = 1 / NUM_ROWS
|
||||
local initialParentPosition = uiDragDetector.Parent.Position
|
||||
|
||||
uiDragDetector.DragStart:Connect(function()
|
||||
initialParentPosition = uiDragDetector.Parent.Position
|
||||
end)
|
||||
|
||||
local function dragToGridOnly(proposedPosition, proposedRotation)
|
||||
local griddedXScale = math.round(proposedPosition.X.Scale / xScaleIncrement) * xScaleIncrement
|
||||
local griddedYScale = math.round(proposedPosition.Y.Scale / yScaleIncrement) * yScaleIncrement
|
||||
|
||||
return UDim2.fromScale(griddedXScale, griddedYScale), proposedRotation
|
||||
end
|
||||
|
||||
uiDragDetector:AddConstraintFunction(1, dragToGridOnly)
|
||||
```
|
||||
70
.cursor/rules/roblox/en-us/ui/video-frames.md
Normal file
70
.cursor/rules/roblox/en-us/ui/video-frames.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: Video frames
|
||||
description: Video assets used on VideoFrame instances allow for video playback in experiences.
|
||||
---
|
||||
|
||||
import BetaAlert from '../includes/beta-features/beta-alert.md'
|
||||
|
||||
Video assets used in `Class.VideoFrame` instances allow for video playback in experiences. You can [upload](#upload-videos) videos that you're certain you have permission to use, such as videos you make yourself, and the [asset privacy](../projects/assets/privacy.md) system automatically ensures that the IDs of your uploaded videos can't be accessed by users without the proper permissions.
|
||||
|
||||
## Upload videos
|
||||
|
||||
<BetaAlert betaName="Video Uploads" leadIn="To upload video assets, enable the beta feature through " leadOut="." components={props.components} />
|
||||
|
||||
If you're a 13+ [ID verified](https://en.help.roblox.com/hc/en-us/articles/4407282410644-Age-ID-Verification) user, you can upload videos through the [Asset Manager](../projects/assets/manager.md), the [Creator Dashboard](https://create.roblox.com/dashboard/creations?activeTab=Video), or the [Open Cloud API](../cloud/guides/usage-assets.md). You can upload a video as long as it meets the following requirements:
|
||||
|
||||
- You have the legal rights to use the video asset.
|
||||
- It adheres to the [Roblox Community Standards](https://en.help.roblox.com/hc/en-us/articles/203313410) and [Terms of Use](https://en.help.roblox.com/hc/en-us/articles/115004647846).
|
||||
- It's 5 minutes or less in either `.mp4` or `.mov` format.
|
||||
- Its resolution is less than or equal to 4096×2160.
|
||||
- It's less than 3.75 GB.
|
||||
|
||||
Videos that don't meet these requirements are rejected. Alpha channels are not supported and will be ignored. When uploading videos, consider the following:
|
||||
|
||||
- Each video upload costs 2,000 Robux.
|
||||
- You can upload a maximum of 20 videos a day.
|
||||
|
||||
## Play videos
|
||||
|
||||
A `Class.VideoFrame` must be parented to a `Class.ScreenGui`, `Class.SurfaceGui`, or `Class.BillboardGui` in order to be playable. Currently, a maximum of two videos can play simultaneously.
|
||||
|
||||
To play a video in your experience without code:
|
||||
|
||||
1. Create a `Class.ScreenGui` as outlined in [On-Screen UI Containers](../ui/on-screen-containers.md), or a `Class.SurfaceGui` or `Class.BillboardGui` as outlined in [In-Experience UI Containers](../ui/in-experience-containers.md).
|
||||
2. Insert a video from the [Toolbox](../projects/assets/toolbox.md) or [Asset Manager](../projects/assets/manager.md). A new `Class.VideoFrame` object is inserted for the video.
|
||||
3. Parent the `Class.VideoFrame` to the container.
|
||||
|
||||
<img src="../assets/studio/explorer/StarterGui-ScreenGui-VideoFrame.png" width="320" />
|
||||
|
||||
4. With the new `Class.VideoFrame` selected, enable its **Looped** and **Playing** properties in the [Properties](../studio/properties.md) window.
|
||||
|
||||
<img src="../assets/studio/properties/VideoFrame-Looped-Playing.png" width="320" />
|
||||
|
||||
If you want to play a video in your experience with code, paste the following code into a `Class.Script` within `Class.ServerScriptService` to create a `Class.Part` and play the video on its front surface.
|
||||
|
||||
```lua
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local screenPart = Instance.new("Part")
|
||||
screenPart.Size = Vector3.new(16, 9, 1)
|
||||
screenPart.Position = Vector3.new(0, 8, -20)
|
||||
screenPart.Orientation = Vector3.new(0, 180, 0)
|
||||
screenPart.Anchored = true
|
||||
screenPart.Parent = Workspace
|
||||
|
||||
local surfaceGui = Instance.new("SurfaceGui")
|
||||
surfaceGui.Parent = screenPart
|
||||
|
||||
local videoFrame = Instance.new("VideoFrame")
|
||||
videoFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
videoFrame.Parent = surfaceGui
|
||||
|
||||
videoFrame.Looped = true
|
||||
videoFrame.Video = "rbxassetid://5608384572" -- Replace with your video's asset ID
|
||||
|
||||
while not videoFrame.IsLoaded do
|
||||
videoFrame.Loaded:Wait()
|
||||
end
|
||||
|
||||
videoFrame:Play()
|
||||
```
|
||||
153
.cursor/rules/roblox/en-us/ui/viewport-frames.md
Normal file
153
.cursor/rules/roblox/en-us/ui/viewport-frames.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: Viewport frames
|
||||
description: ViewportFrames use a camera to render 3D objects into a 2D viewport.
|
||||
---
|
||||
|
||||
A `Class.ViewportFrame` uses a camera to render 3D objects into a 2D viewport. Ideal use cases include:
|
||||
|
||||
- A minimap of your experience directly in the corner of a user's screen.
|
||||
- 3D models of items in an inventory menu.
|
||||
- Rotating objects that a character has equipped.
|
||||
|
||||
## Viewport configurations
|
||||
|
||||
3D objects that users view through a `Class.ViewportFrame` can either move with their camera, remain static, or rotate within the `Class.ViewportFrame`. This object can also include a `Class.Sky` child as a cubemap for reflections.
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="With Camera">
|
||||
|
||||
If you want a 3D object to move with the camera:
|
||||
|
||||
1. Position your camera view within the experience so that the object you want to see within the frame is visible.
|
||||
2. Add a new `Class.ViewportFrame` to the [screen](../ui/on-screen-containers.md) and then make sure it's selected in the **Explorer**.
|
||||
3. In the **Properties** window, assign the `Class.ViewportFrame.CurrentCamera|CurrentCamera` property to the camera:
|
||||
|
||||
1. Select the `Class.ViewportFrame.CurrentCamera|CurrentCamera` property. Your cursor changes.
|
||||
2. In the **Explorer** window, click on the top-level `Class.Camera` object.
|
||||
|
||||
4. Parent the desired 3D object to the new `Class.ViewportFrame`. Note that if you still want to see the object within your experience, you must duplicate it in the `Class.Workspace` and then parent the **duplicate** object to the `Class.ViewportFrame`.
|
||||
|
||||
When you move your camera, the object will also move within the `Class.ViewportFrame`.
|
||||
|
||||
<Alert severity="warning">
|
||||
When you want to update the view of your `Class.ViewportFrame`, be sure to update the camera, **not** the objects within the view.
|
||||
</Alert>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Static">
|
||||
|
||||
If you want the 3D object to remain static:
|
||||
|
||||
1. Position your camera view within the experience so that the object you want to see is in the exact position you want to see it within the frame.
|
||||
2. In the **Explorer** window, duplicate the top-level `Class.Camera` object, then rename it to an identifiable name like `ViewportCam`.
|
||||
3. Add a new `Class.ViewportFrame` to the [screen](../ui/on-screen-containers.md) and then make sure it's selected in the **Explorer**.
|
||||
4. In the **Properties** window, assign the frame's `Class.ViewportFrame.CurrentCamera|CurrentCamera` property to the **duplicated** camera:
|
||||
|
||||
1. Select the `Class.ViewportFrame.CurrentCamera|CurrentCamera` property. Your cursor changes.
|
||||
2. In the **Explorer** window, click on the duplicated camera object.
|
||||
|
||||
5. Parent the desired 3D object to the new `Class.ViewportFrame`. Note that if you still want to see the object within your experience, you must duplicate it in the `Class.Workspace` and then parent the **duplicate** object to the `Class.ViewportFrame`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Rotation">
|
||||
|
||||
If you want a 3D object such as a `Class.BasePart` to rotate on its own within the frame:
|
||||
|
||||
1. Add a new `Class.ViewportFrame` to the [screen](../ui/on-screen-containers.md).
|
||||
2. In the **Explorer** window, drag the desired `Class.BasePart` into the new `Class.ViewportFrame`.
|
||||
3. Insert a new `Class.LocalScript` into the `Class.ViewportFrame` and paste in the following code.
|
||||
|
||||
```lua
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local viewportFrame = script.Parent
|
||||
|
||||
-- Parameters to experiment with
|
||||
local cameraDistance = 10
|
||||
local cameraFieldOfView = 50
|
||||
local objectPitchAngle = 40
|
||||
local objectRotationSpeed = 50
|
||||
|
||||
-- Viewport camera initialization
|
||||
local viewportCamera = Instance.new("Camera")
|
||||
viewportCamera.FieldOfView = cameraFieldOfView
|
||||
viewportFrame.CurrentCamera = viewportCamera
|
||||
viewportCamera.Parent = viewportFrame
|
||||
|
||||
-- Viewport object initialization
|
||||
local object = viewportFrame:FindFirstChildWhichIsA("BasePart")
|
||||
if object then
|
||||
object.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(objectPitchAngle), 0, 0)
|
||||
|
||||
-- Update loop
|
||||
local t = 0
|
||||
RunService.PostSimulation:Connect(function(delta)
|
||||
t += delta
|
||||
viewportCamera.CFrame = CFrame.Angles(0, math.rad(t * objectRotationSpeed), 0) * CFrame.new(0, 0, cameraDistance)
|
||||
end)
|
||||
else
|
||||
warn("3D object not found as child of viewport frame")
|
||||
end
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Skybox Reflections">
|
||||
|
||||
`Class.ViewportFrame|ViewportFrames` can use a `Class.Sky` child as a cubemap for reflections, in which case only the `Class.Sky` object's six `Skybox[…]` properties are used. Assuming these properties are valid, lighting inside the `Class.ViewportFrame` acts similarly to when `Class.Lighting.EnvironmentSpecularScale` and `Class.Lighting.EnvironmentDiffuseScale` are both set to `1`.
|
||||
|
||||
To implement skybox cubemap reflections:
|
||||
|
||||
1. Insert a `Class.Sky` object as a direct child of the `Class.ViewportFrame`.
|
||||
1. Set the `Class.Sky` object's six texture properties (`Class.Sky.SkyboxBk|SkyboxBk`, `Class.Sky.SkyboxDn|SkyboxDn`, `Class.Sky.SkyboxFt|SkyboxFt`, `Class.Sky.SkyboxLf|SkyboxLf`, `Class.Sky.SkyboxRt|SkyboxRt`, `Class.Sky.SkyboxUp|SkyboxUp`).
|
||||
1. For `Class.Part|Parts` that should appear within the frame, set their `Class.BasePart.Reflectance|Reflectance` property greater than 0, or use a reflectant material like `Enum.Material.Glass|Glass` or `Enum.Material.Foil|Foil`. For `Class.MeshPart|MeshParts` that should appear within the frame, apply a `Class.SurfaceAppearance` with a properly-configured `Class.SurfaceAppearance.MetalnessMap|MetalnessMap`.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Lighting and appearance
|
||||
|
||||
Lighting within a `Class.ViewportFrame` is controlled through three properties:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.ViewportFrame.Ambient|Ambient`</td>
|
||||
<td>Determines the overall lighting hue applied to the area within the viewport frame. Defaults to <Typography noWrap>`Datatype.Color3.fromRGB(200, 200, 200)`</Typography> (ghost grey).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ViewportFrame.LightDirection|LightDirection`</td>
|
||||
<td>A `Datatype.Vector3` representing the direction of the light source from position <Typography noWrap>`(0, 0, 0)`</Typography>. Defaults to <Typography noWrap>`(-1, -1, -1)`</Typography>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ViewportFrame.LightColor|LightColor`</td>
|
||||
<td>Color of the directional light. Defaults to <Typography noWrap>`Datatype.Color3.fromRGB(140, 140, 140)`</Typography> (silver).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Additionally, you can adjust the overall rendered appearance of the viewport throuugh the following properties:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>`Class.ViewportFrame.ImageColor3|ImageColor3`</td>
|
||||
<td>Changes the image color/tint without modification of the rendered object. The default colorization value is <Typography noWrap>`Datatype.Color3.new(1, 1, 1)`</Typography> (white) at which no color modification occurs.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`Class.ViewportFrame.ImageTransparency|ImageTransparency`</td>
|
||||
<td>Changes the image transparency without modification of the rendered object. A value of `0` (default) is completely opaque and a value of `1` is completely transparent (invisible).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Loading…
Add table
Add a link
Reference in a new issue