8.04. Справочник по Roblox
Справочник по Roblox
Часть 1: Roblox Lua и базовый класс Instance
1.1 Roblox Lua — среда выполнения
-
Версия Lua: 5.1, с рядом ограничений и расширений.
-
Запрещены стандартные библиотеки:
os.*,io.*,debug.*,package.*,loadstringcollectgarbage— ограничена (collectgarbage("count")разрешён, остальные вызовы — нет)
-
Добавлены глобальные таблицы/функции:
game -- корневой Instance
workspace -- алиас game.Workspace
script -- ссылка на текущий скрипт (Script/LocalScript/ModuleScript)
shared -- устарело, не использовать
settings -- deprecated, заменено на game:GetService("StarterGui"):GetCore(...) -
Глобальные сервисы (доступны через
game:GetService("Имя")):
Workspace,Players,ReplicatedStorage,ServerStorage,Lighting,StarterGui,StarterPlayer,TweenService,PathfindingService,DataStoreService,HttpService,MarketplaceService,UserInputService,RunService,Stats,LocalizationService,BadgeService,Chat,CollectionService,ContentProvider,Debris,InsertService,LogService,PhysicsService,SoundService,TestServiceи др. -
Типы скриптов:
Тип Выполняется на Доступ к Основное назначение ScriptServer game.ServerStorage,Workspace,Players,ReplicatedStorage(только чтение в ServerScriptService),DataStoreServiceСерверная логика, безопасные операции LocalScriptClient PlayerGui,StarterGui,Players.LocalPlayer,ReplicatedFirst,ReplicatedStorageUI, ввод, рендер-эффекты, предварительная валидация ModuleScriptОба Только при require(). Может возвращать таблицу/функцию.Повторное использование кода, инкапсуляция -
Модель потока выполнения:
- Сервер создаёт и управляет основной иерархией
game. - Клиент получает реплицированные объекты (всё, что в
Workspace,ReplicatedStorage,Lighting,StarterGuiи т.п.). - Изменения свойств
Instance, помеченных как Replicated, автоматически синхронизируются сервер→клиент. - Обратная синхронизация (клиент→сервер) — только через
RemoteEvent/RemoteFunction.
- Сервер создаёт и управляет основной иерархией
1.2 Instance — базовый класс всех объектов
Все объекты в Roblox — наследники Instance.
Нельзя создать Instance напрямую: Instance.new() вызывает ошибку. Используется Instance.new("Part") и т.п.
1.2.1 Свойства Instance
| Имя | Тип | Доступ | Описание |
|---|---|---|---|
Name | string | RW | Имя экземпляра (не уникальное, но влияет на :FindFirstChild() и .-доступ) |
Parent | Instance | nil | RW | Родительский объект. При nil — объект отсоединён (Destroy() удаляет поддерево) |
ClassName | string | RO | Имя класса (например, "Part"). Неизменяемо. |
Archivable | boolean | RW | Указывает, может ли объект быть сериализован (:Clone(), :GetAttribute(), сохранение в .rbxl). По умолчанию true, но для Player, Humanoid и др. — false. |
AncestryChanged | RBXScriptSignal | RO | Сигнал: (child: Instance, parent: Instance | nil). Вызывается при изменении Parent. |
Changed | RBXScriptSignal | RO | Сигнал: (propertyName: string). Срабатывает при изменении любого свойства (кроме Parent, Name, ClassName). |
⚠️ Свойства
Anchored,Position,CFrame,Size,Velocityи др. — не свойстваInstance, а специфичны для наследников (например,BasePart).
1.2.2 Методы Instance
| Метод | Подпись | Контекст | Описание |
|---|---|---|---|
:Clone() | → Instance | Both | Возвращает глубокую копию объекта и всех потомков. Объекты с Archivable = false копируются как пустые заглушки (nil → Instance.new("Folder")). |
:Destroy() | → void | Both | Удаляет объект и всех потомков. Вызывает AncestryChanged с parent = nil, затем освобождает память. Безопасен к повторным вызовам. |
:FindFirstChild(name: string, recursive: boolean = false) | → Instance | nil | Both | Поиск дочернего элемента по Name. Если recursive = true — рекурсивно. Не выдаёт ошибку, если не найдено. |
:WaitForChild(name: string, timeout: number = ∞) | → Instance | Both | Блокирует поток до появления дочернего объекта с указанным именем или таймаута (в секундах). При таймауте — ошибка. |
:IsA(className: string) | → boolean | Both | Проверяет, является ли объект экземпляром указанного класса (учитывает наследование). Эквивалентно typeof(obj) == className для конкретного класса, но работает с иерархией. |
:IsDescendantOf(ancestor: Instance) | → boolean | Both | Проверяет, находится ли объект в поддереве ancestor. |
:GetChildren() | → {Instance} | Both | Возвращает таблицу непосредственных потомков (1 уровень вниз). |
:GetDescendants() | → {Instance} | Both | Возвращает всех потомков (рекурсивно, в порядке обхода "в глубину"). |
:GetFullName() | → string | Both | Возвращает путь от корня, например "Workspace.Part.Script". Полезно для логирования. |
:ClearAllChildren() | → void | Both | Удаляет всех непосредственных потомков (:Destroy() для каждого). |
:GetPropertyChangedSignal(property: string) | → RBXScriptSignal | Both | Возвращает сигнал, который сработает при изменении указанного свойства. Сигнал: (). Позволяет отслеживать изменения без polling. |
:GetAttribute(attributeName: string) | → any | Both | Получает значение пользовательского атрибута (хранится в реплицированной таблице). |
:SetAttribute(attributeName: string, value: any) | → void | Server | Устанавливает пользовательский атрибут. Поддерживаемые типы: string, number, bool, Vector3, CFrame, UDim, UDim2, Ray, Faces, Axes, BrickColor, Color3, NumberSequence, ColorSequence, NumberRange, Rect, PhysicalProperties, ColorSequenceKeypoint, NumberSequenceKeypoint. |
:GetAttributes() | → {[string]: any} | Both | Возвращает копию таблицы всех атрибутов. |
:GetTags() | → {string} | Both (требует CollectionService) | Возвращает теги, назначенные через CollectionService:AddTag(). |
:IsA() — уже выше. Дополнительно: :IsAncestorOf(descendant: Instance) → boolean — зеркально :IsDescendantOf(). |
1.2.3 События Instance
События — это свойства типа RBXScriptSignal. Подключение:
local connection = instance.Changed:Connect(function(propertyName)
print("Изменено:", propertyName)
end)
connection:Disconnect() -- отключить вручную
AncestryChanged: RBXScriptSignal(child: Instance, parent: Instance \| nil)Changed: RBXScriptSignal(propertyName: string)
Важно:
Changedне срабатывает при измененииParent. ДляParent— толькоAncestryChanged.
1.3 Основные принципы работы с Instance
-
Родительская привязка (
Parent):- Установка
ParentдоNameне влияет на доступ черезworkspace.PartName— поиск происходит после полной инициализации. - Установка
Parent = nilне уничтожает объект, но делает его невидимым для:FindFirstChild(),:GetChildren()и рендерера. - Объект без
Parentне реплицируется клиентам.
- Установка
-
Репликация:
- Только объекты в
Workspace,ReplicatedStorage,Lighting,StarterGui(и их потомки) реплицируются клиентам. - Изменения
CFrame,Velocity,Anchored,CanCollide,TransparencyуPartреплицируются автоматически (сервер → клиент). - Свойства
Value(StringValue,NumberValueи др.) реплицируются автоматически. - Изменения в
ServerStorage,ServerScriptService— не реплицируются.
- Только объекты в
-
Производительность:
:GetDescendants()— O(N), избегать в hot loop.:FindFirstChild()безrecursive = true— O(1) (хэш-таблица по имени).:WaitForChild()с таймаутом — предпочтительнее polling.- Избегать
game:GetService("Players")в LocalScript (лучшеPlayers = game:GetService("Players")один раз в начале).
Часть 2: Пространственные объекты и физика
Эта часть охватывает пространственную иерархию, геометрию, движение, взаимодействие и навигацию в Roblox.
Все объекты, участвующие в 3D-мире, наследуются от Instance, но ключевая иерархия строится от BasePart.
2.1 Иерархия BasePart
Instance
└── PVInstance
└── BasePart
├── Part
├── MeshPart
├── TrussPart
└── UnionOperation (не BasePart напрямую, но создаёт объект, являющийся BasePart)
PVInstance(Physical Volume Instance) — внутренний промежуточный класс, добавляющий базовые физические свойства (Position,Rotation,Velocity). Не документирован официально, но отражён в отладчике.
2.1.1 BasePart — общий интерфейс для физических объектов
Все BasePart участвуют в физике (если Anchored = false) и рендеринге.
Свойства BasePart
| Свойство | Тип | Доступ | Контекст | Описание |
|---|---|---|---|---|
Position | Vector3 | RW | Replicated | Позиция центра масс (в мировых координатах). Устанавливать можно, но предпочтительно — через CFrame. |
CFrame | CFrame | RW | Replicated | Полная поза: позиция + ориентация. Основной способ управления положением и поворотом. Изменение CFrame перезаписывает Position и Rotation. |
Size | Vector3 | RW (но не во время симуляции, если Anchored = false) | Replicated | Размер по осям X, Y, Z в стадиях (1 стадия = 1 метр). Минимум — 0.001, 0.001, 0.001. |
Rotation | Vector3 | RO | Replicated | Эйлеровы углы (в градусах) относительно мировой системы координат. Вычисляется из CFrame. Изменять напрямую нельзя — только через CFrame. |
Anchored | boolean | RW | Replicated | Если true — объект игнорирует гравитацию и физику, остаётся неподвижным. По умолчанию false. |
Velocity | Vector3 | RW | Replicated | Линейная скорость центра масс (стадий/сек). Изменяется физикой или вручную. |
RotVelocity | Vector3 | RW | Replicated | Угловая скорость (рад/сек) вокруг осей X, Y, Z в локальной системе. |
Mass | number | RO | Replicated | Вычисляется как Density × Volume. Зависит от Size и Material. |
Density | number | RW | Both | Плотность (кг/стадия³). По умолчанию: Wood = 600, Plastic = 1150, Metal = 7850 и др. Изменение Density немедленно меняет Mass. |
Material | Enum.Material | RW | Replicated | Определяет визуальную текстуру, звук и физические параметры (коэфф. трения, restitution). |
Color | Color3 | RW | Replicated | Основной цвет поверхности (если BrickColor не установлен). Приоритет ниже BrickColor. |
BrickColor | BrickColor | RW | Replicated | Цвет из палитры LEGO (999+ значений). Приоритет выше Color. |
Transparency | number (0.0–1.0) | RW | Replicated | Прозрачность. 0 — непрозрачный, 1 — полностью прозрачный. Влияет на рендер и коллизии (CanCollide). |
Reflectance | number (0.0–1.0) | RW | Replicated | Коэффициент отражения (0 = матовый, 1 = зеркальный). Только для материалов SmoothPlastic, Neon, Glass, Metal. |
CanCollide | boolean | RW | Replicated | Включение/выключение коллизий с другими BasePart. Если false — проходит сквозь всё (но Touched/TouchEnded не срабатывают). |
CanQuery | boolean | RW | Replicated | Участвует ли в Workspace:Raycast(), :FindPartsInRegion3() и т.п. По умолчанию true. |
CanTouch | boolean | RW | Replicated | Генерировать ли события Touched/TouchEnded. По умолчанию true. Важно: при false коллизии всё ещё возможны (CanCollide = true). |
Shape | Enum.PartType | RO | — | Форма: Block, Ball, Cylinder, CustomMesh, Truss. У Part — читаемое, у MeshPart — всегда CustomMesh. |
Locked | boolean | RW | Both | Блокировка в Studio (не влияет на рантайм). Игнорируется в игре. |
RootPriority | number | RW (только для Assembly) | Replicated | Приоритет корня ассемблии (для Humanoid:MoveTo() и IK). Не документирован, используется внутренне. |
Методы BasePart
| Метод | Подпись | Контекст | Описание |
|---|---|---|---|
:GetConnectedParts(recursive: boolean = false) | → {BasePart} | Both | Возвращает части, соединённые WeldConstraint, RodConstraint, BallSocketConstraint и др. При recursive = true — транзитивное замыкание. |
:GetJoints() | → {Constraint} | Both | Возвращает все Constraint, прикреплённые к части (через Attachment). |
:GetMass() | → number | Both | То же, что свойство Mass, но как метод (для совместимости). |
:GetRealMass() | → number | Both | Учитывает присоединённые Constraint и другие части в ассемблии. |
:GetTouchingParts() | → {BasePart} | Both | Возвращает части, с которыми активно происходит коллизия (контактные точки ≠ 0). Требует CanCollide = true. |
:Subtract(part: BasePart) | → UnionOperation | Server | Создаёт UnionOperation, выполняющий булево вычитание: self – part. |
:Union(parts: {BasePart}) | → UnionOperation | Server | Объединяет self и parts в UnionOperation. |
:Intersect(part: BasePart) | → UnionOperation | Server | Пересечение self ∩ part. |
:Resize(extent: Vector3) | → void | Server | Увеличивает Size на extent (в стадиях) относительно центра. Устаревший, предпочтительно Size += extent. |
:ApplyImpulse(impulse: Vector3) | → void | Replicated | Прикладывает импульс (в кг·стадий/сек) к центру масс. Эффективно Velocity += impulse / Mass. |
:ApplyImpulseAtPosition(impulse: Vector3, position: Vector3) | → void | Replicated | Прикладывает импульс в указанной мировой позиции → вызывает вращение. |
:ApplyAngularImpulse(impulse: Vector3) | → void | Replicated | Прикладывает угловой импульс (в кг·стадий²/сек) → изменяет RotVelocity. |
:GetBoundingBox() | → (Vector3 center, Vector3 size) | Both | Возвращает осе-выровненный ограничивающий параллелепипед (AABB) части в мировых координатах. |
:GetExtents() | → (Vector3 min, Vector3 max) | Both | То же, что GetBoundingBox, но возвращаются углы. |
:ToWorldSpace(cf: CFrame) | → CFrame | Both | Преобразует локальную cf (относительно части) в мировую систему координат. Эквивалентно CFrame * cf. |
:ToObjectSpace(cf: CFrame) | → CFrame | Both | Обратное: CFrame:Inverse() * cf. |
:GetVelocityAtPosition(position: Vector3) | → Vector3 | Both | Линейная скорость точки с мировой координатой position, учитывая вращение (Velocity + RotVelocity × (position – Position)). |
События BasePart
| Событие | Подпись | Контекст | Описание |
|---|---|---|---|
Touched | (otherPart: BasePart) | Replicated | Срабатывает при первой коллизии с другой частью (в один кадр — один вызов, даже при множестве контактов). |
TouchEnded | (otherPart: BasePart) | Replicated | Срабатывает, когда коллизия с otherPart завершается. |
Stepped | (dt: number) | Server | Устаревшее. Заменено на RunService.Stepped. Не использовать. |
LocalSpaceMoved | (deltaCFrame: CFrame) | Client | Только для Part в ReplicatedFirst. Используется для предиктивного рендеринга. Редко. |
⚠️
Touched/TouchEndedтребуют:
CanCollide = trueу обоих участниковCanTouch = trueу обоих- Физическое пересечение геометрии (не AABB, а точная форма: mesh/collision volume)
- Один из объектов должен быть
Anchored = falseили оба — но с движением (иначе срабатывает только при ручном измененииCFrame)
2.1.2 Part — стандартный блок
Наследует BasePart. Добавляет только Shape (доступен для записи у Part, но не у MeshPart).
Shape:Enum.PartType.Block | Ball | Cylinder- При смене
Shapeпересчитывается collision mesh иMass. BallиCylinderиспользуют точные формы для коллизий (не AABB).Block— быстрее всего в расчётах.
- При смене
Пример: создание шара
local sphere = Instance.new("Part")
sphere.Shape = Enum.PartType.Ball
sphere.Size = Vector3.new(2, 2, 2) -- диаметр = 2
sphere.Position = Vector3.new(0, 10, 0)
sphere.Anchored = false
sphere.Parent = workspace
2.1.3 MeshPart
Shapeфиксирован какCustomMesh.- Требует наличие
SpecialMeshилиDataModelMesh(например,FileMesh) как дочернего объекта для определения геометрии. - Если
SpecialMeshотсутствует — отображается как пустойBlock(но коллизии — только по AABB). - Коллизии по умолчанию — AABB, даже если mesh сложный. Для точных коллизий:
- В Studio: *CollisionFidelity → PreciseConvexDecomposition или Hull
- Через код — нельзя, только через редактор или загрузку
.rbxm.
2.1.4 UnionOperation и NegateOperation
- Используются для булевых операций над
BasePart. - Не являются
BasePartнапрямую, но при вызове:Union(),:Subtract()и т.п. возвращают новый объект типаPart(сShape = Enum.PartType.Block, но с кастомным collision mesh). - Возвращаемый
PartимеетCanCollide = true,Anchored = false,Transparency = 0. - Удаление исходных частей не ломает union — он автономен.
- Union нельзя редактировать после создания (только
Destroyи пересоздавать).
2.2 Model — контейнер для иерархии
Instance
└── Model
├── PrimaryPart (не обязательно, но важно для :MoveTo(), :GetModelCFrame())
└── другие BasePart, Attachment, Constraint...
Свойства Model
| Свойство | Тип | Описание |
|---|---|---|
PrimaryPart | BasePart | nil | Часть, относительно которой определяется поза модели. Если не задана — выбирается первая BasePart. |
Archivable | boolean | По умолчанию true, но если в модели есть Player, Humanoid — становится false. |
Методы Model
| Метод | Подпись | Описание |
|---|---|---|
:GetModelCFrame() | → CFrame | Возвращает CFrame модели: позиция = PrimaryPart.Position, ориентация = PrimaryPart.CFrame.Rotation. Если PrimaryPart нет — возвращает CFrame.new(). |
:SetPrimaryPartCFrame(cf: CFrame) | → void | Перемещает всю модель, выравнивая PrimaryPart по cf. Остальные части смещаются относительно неё. |
:MoveTo(position: Vector3) | → void | Эквивалентно :SetPrimaryPartCFrame(CFrame.new(position) * PrimaryPart.CFrame.Rotation). |
:GetBoundingBox() | → (Vector3 center, Vector3 size) | AABB всей модели (все BasePart). |
:PivotTo(cf: CFrame) | → void | Новый (2022+) способ: использует Model:PivotOffset и PrimaryPart. Предпочтительнее :SetPrimaryPartCFrame(). |
:GetPivot() | → CFrame | Возвращает текущий Pivot CFrame. |
:GetDescendantAddedSignal() | → RBXScriptSignal | Сигнал: (descendant: Instance). Срабатывает при добавлении любого потомка (в т.ч. второго уровня). |
:PivotTo()и:GetPivot()— рекомендуемый современный API для позиционирования моделей. Совместим с IK, анимациями,HumanoidRootPart.
2.3 Attachment — точки привязки
Используется для:
- Привязки
Constraint(шарниры, пружины) - Определения точек излучения
ParticleEmitter,Tool,ProximityPrompt - Задания локальных систем координат
Свойства Attachment
| Свойство | Тип | Описание |
|---|---|---|
WorldPosition | Vector3 | RO. Мировая позиция точки. |
WorldCFrame | CFrame | RO. Полная мировая поза. |
Position | Vector3 | RW. Локальная позиция (относительно Parent — BasePart). |
Rotation | Vector3 | RW. Локальный поворот (в градусах, Эйлер). |
CFrame | CFrame | RW. Локальная поза. Приоритет выше Position/Rotation. |
Axis | Vector3 | RO. Нормализованная ось X WorldCFrame. |
SecondaryAxis | Vector3 | RO. Нормализованная ось Y WorldCFrame. |
Attachmentне рендерится, не участвует в коллизиях. Чисто логический объект.
2.4 Пространственные типы данных
Vector3
- Конструкторы:
Vector3.new(x, y, z)Vector3.zero→Vector3.new(0, 0, 0)Vector3.one→Vector3.new(1, 1, 1)
- Операции:
+,-,*(скаляр и вектор),/,==,-(унарный) - Методы:
:Magnitude()→ длина:Unit()→ нормализованный вектор:Dot(v)→ скалярное произведение:Cross(v)→ векторное произведение:Lerp(goal, alpha)→ линейная интерполяция:Distance(v)→|(self - v)|:DistanceTo(v)→ alias:Distance():Max(other)/:Min(other)→ покомпонентные max/min
CFrame
- Комбинирует позицию (Vector3) и ориентацию (3×3 rotation matrix).
- Конструкторы:
CFrame.new()→ identityCFrame.new(x, y, z)CFrame.new(Vector3 pos)CFrame.new(pos, lookAt)→ смотрит изposвlookAtCFrame.new(pos, r00, r01, r02, r10, ..., r22)— прямо матрицуCFrame.Angles(rx, ry, rz)→ только поворот (позиция = 0)CFrame.fromMatrix(pos, vX, vY [, vZ])— из базиса
- Свойства:
.Position→Vector3.XVector,.YVector,.ZVector→ оси базиса.RightVector,.UpVector,.LookVector— алиасы (Look = Z)
- Методы:
*(умножение) — композиция трансформаций:Inverse():ToWorldSpace(cf)/:ToObjectSpace(cf):Lerp(goal, alpha):GetComponents()→(x,y,z, r00,...,r22):ToEulerAnglesXYZ()/YXZ()/ZYX()→ (rx, ry, rz) в радианах:ToAxisAngle()→(axis: Vector3, angle: number):PointToWorldSpace(v)→self * v:VectorToWorldSpace(v)→self - self.Position * v(без сдвига)
Region3
- Определён двумя
Vector3:CFrame+Size→Region3.new(min, max). minиmax— противоположные углы AABB (не обязательно упорядочены; конструктор сам сортирует).- Используется в:
Workspace:FindPartsInRegion3(region, ignoreDescendant, maxParts = 100)Workspace:FindPartOnRay(ray, ignoreDescendant)— устаревшее, замененоRaycast
- ⚠️ Не поддерживает поворот — только осе-выровненные регионы.
Region3int16
- Целочисленная версия
Region3, используемая вTerrain:ReadVoxels(). - Координаты —
int16(от -32768 до 32767). Region3int16.new(min: Vector3int16, max: Vector3int16)
2.5 Расчёт столкновений: Raycasting
Старый API (FindPartOnRay) устарел. Используется Workspace:Raycast() и RaycastParams.
RaycastParams
- Создаётся через
RaycastParams.new(). - Свойства:
.FilterDescendantsInstances = {Instance}— список объектов, которые игнорируются.FilterType = Enum.RaycastFilterType.Whitelist \| BlacklistWhitelist: проверяются только объекты изFilterDescendantsInstancesBlacklist: проверяются все, кроме указанных
.IgnoreWater = boolean.CollidableOnly = boolean— учитывать толькоCanCollide = true
- Методы: нет (immutable после передачи в
Raycast)
Workspace:Raycast(origin: Vector3, direction: Vector3, params: RaycastParams)
- Возвращает
RaycastResult \| nil. direction— не позиция цели, а вектор направления (длина = длина луча).local origin = Vector3.new(0, 5, 0)
local target = Vector3.new(10, 5, 0)
local result = workspace:Raycast(origin, target - origin)
RaycastResult
| Свойство | Тип | Описание |
|---|---|---|
Instance | BasePart | Часть, в которую попал луч |
Position | Vector3 | Точка пересечения |
Normal | Vector3 | Нормаль поверхности в точке пересечения |
Distance | number | Расстояние от origin до Position |
Material | Enum.Material | Материал поверхности |
Face | Enum.NormalId | Грань блока (Top, Bottom, Front, Back, Left, Right) |
2.6 Навигация: PathfindingService
Требует настройки PathfindingLink и NavMesh (в Studio → Pathfinding).
Основные шаги
- Получить
PathfindingService = game:GetService("PathfindingService") - Создать
path = PathfindingService:CreatePath() - Настроить параметры
path:AgentRadius,AgentHeight,AgentCanJump,WaypointSpacing
- Вызвать
path:ComputeAsync(startPos, endPos)— yield-функция - Проверить
path.Status == Enum.PathStatus.Success - Получить точки:
local waypoints = path:GetWaypoints()
PathWaypoint
Таблица:
.Position: Vector3.Action: Enum.PathWaypointAction—Walk,Jump,WalkToPart,Stop.Label: string— дляPathfindingLink.IsRisky: boolean— если путь ведёт в опасную зону (вода, лава)
Часть 3: Персонаж — Humanoid, Animator, Animation и связанные компоненты
Эта часть описывает стандартную модель персонажа Roblox, включая анимационную систему, физику движения, состояние и взаимодействие.
Акцент — на стабильность, безопасность и соответствие официальной архитектуре (StarterPlayer → Character).
3.1 Иерархия персонажа
Стандартный персонаж — Model, создаваемый автоматически из StarterPlayer.StarterCharacterScripts и StarterPlayer.StarterCharacter.
Типичная структура (упрощённо):
Character (Model)
├── HumanoidRootPart (Part) ← PrimaryPart модели
├── Humanoid (Humanoid) ← логика состояния и движения
├── Animator (Animator) ← управление анимациями
├── Head (Part)
│ └── FaceCenterAttachment (Attachment)
├── Torso (Part)
│ ├── RootAttachment (Attachment)
│ ├── Waist (Motor6D)
│ └── [другие Motor6D]
├── [Left/Right] Arm/Leg (Part)
│ └── [Shoulder/Hip/Elbow/Knee] (Motor6D)
├── [Left/Right] Grip (Attachment) — для Tool
└── [Hat, Tool] — динамически добавляются
⚠️ Изменение этой структуры (например, замена
HumanoidRootPart) ломает стандартное поведение (Humanoid:MoveTo(),Animator, анимации). Для кастомных персонажей используйтеR15/RthroиHumanoid:BuildRigFromAttachments().
3.2 Humanoid — ядро персонажа
Humanoid управляет:
- Состоянием (
Health,MaxHealth,Sit,Jump,PlatformStand) - Движением (
MoveDirection,WalkSpeed,JumpPower) - Анимацией (через
Animator) - Воскрешением
- Синхронизацией между сервером и клиентом
3.2.1 Свойства Humanoid
| Свойство | Тип | Доступ | Контекст | Описание |
|---|---|---|---|---|
Health | number | RW | Replicated | Текущее здоровье. При ≤ 0 → :TakeDamage(), затем BreakJoints() и Dead = true. |
MaxHealth | number | RW | Replicated | Максимум здоровья. По умолчанию 100. |
WalkSpeed | number | RW | Replicated | Скорость ходьбы (стадий/сек). По умолчанию 16. Максимум 100 (ограничение клиента). |
JumpPower | number | RW | Replicated | Импульс прыжка (стадий/сек). По умолчанию 50. Максимум 200. |
AutoJumpEnabled | boolean | RW | Client | Разрешить автоматический прыжок при столкновении с препятствием (если MoveDirection.Y > 0). |
AutoRotate | boolean | RW | Replicated | Повернуть персонажа в направлении MoveDirection (если true). По умолчанию true. |
MoveDirection | Vector3 | RW | Client | Направление движения (нормализовано, локальная система координат персонажа). Устанавливается StarterPlayerScripts из ввода. |
Sit | boolean | RW | Replicated | Если true — персонаж садится (игнорирует гравитацию, MoveDirection игнорируется). |
Jump | boolean | RW | Client → Server | Запрос прыжка. Устанавливается в true клиентом, сервер реплицирует и применяет физику. Автоматически сбрасывается в false. |
PlatformStand | boolean | RW | Replicated | Игнорировать гравитацию, но оставаться в воздухе (для платформеров). |
State | Enum.HumanoidStateType | RO | Replicated | Текущее состояние: None, Dead, GettingUp, Jumping, Freefall, FallingDown, Climbing, Seated, PlatformStanding, Swimming, Flying, Running. |
RootPart | BasePart | nil | RO | Replicated | Часть, используемая как точка опоры (по умолчанию HumanoidRootPart). |
RigType | Enum.HumanoidRigType | RO | — | R6 (6 частей), R15 (15 частей), Rthro. Задаётся при создании. |
DisplayName | string | RW | Replicated | Отображаемое имя (не Player.Name). |
DisplayDistanceType | Enum.HumanoidDisplayDistanceType | RW | Replicated | Viewer, Subject, None — управляет отображением имени. |
BreakJointsOnDeath | boolean | RW | Server | Разрушать ли Motor6D при смерти (по умолчанию true). |
HealthDisplayDistance | number | RW | Replicated | Дистанция, на которой видна полоска здоровья (в стадиях). 0 — всегда, -1 — никогда. |
3.2.2 Методы Humanoid
| Метод | Подпись | Контекст | Описание |
|---|---|---|---|
:TakeDamage(amount: number) | → void | Server | Наносит урон. Вызывает событие TakeDamage, затем проверяет Health ≤ 0. |
:Heal(amount: number) | → void | Server | Восстанавливает здоровье (не выше MaxHealth). |
:MoveTo(position: Vector3, target: Instance = nil) | → void | Server | Асинхронное движение к позиции. Использует PathfindingService, если доступен. Останавливается при State ≠ Running. |
:Move(direction: Vector3, ignoreAutoRotate: boolean = false) | → void | Client | Устанавливает MoveDirection. Устаревший (до 2019), предпочтительно прямая запись в MoveDirection. |
:ChangeState(state: Enum.HumanoidStateType) | → boolean | Server | Принудительно меняет состояние. Возвращает true, если успешно. Опасно — может нарушить консистентность. |
:BuildRigFromAttachments(definition: R15HumanoidDescription | nil) | → void | Server | Перестраивает скелет по Attachment (требует RigType = R15). Используется для кастомных R15-моделей. |
:GetState() | → Enum.HumanoidStateType | Both | То же, что свойство State. |
:GetPlayingAnimationTracks() | → {AnimationTrack} | Both | Возвращает активные треки анимаций. |
:LoadAnimation(animation: Animation) | → AnimationTrack | Both | Загружает анимацию для проигрывания. Возвращает AnimationTrack. |
:ReplaceFace(faceId: number) | → void | Server | Меняет лицо (для R6). Для R15 — используйте FaceCenterAttachment. |
:GetAccessories() | → {Accessory} | Both | Возвращает одетые аксессуары (шляпы и др.). |
:RemoveAccessories() | → void | Server | Снимает все аксессуары. |
3.2.3 События Humanoid
| Событие | Подпись | Контекст | Описание |
|---|---|---|---|
Died | () | Replicated | Срабатывает при Health ≤ 0. После BreakJoints(). |
Seated | (active: boolean, seatPart: BasePart) | Replicated | При посадке/вставании. |
Jumping | () | Replicated | При начале прыжка (не в апексе). |
FreeFalling | () | Replicated | При входе в состояние Freefall. |
Landed | (fallDistance: number) | Replicated | При приземлении после падения. fallDistance — высота падения. |
Running | (isRunning: boolean) | Replicated | При начале/остановке бега. |
TakeDamage | (amount: number, source: Instance, projectile: BasePart) | Server | Перед Health -= amount. Можно отменить через return false в BindAction. |
HealthChanged | (newHealth: number) | Replicated | При любом изменении Health (включая Heal). |
Touched | (hitPart: BasePart) | Replicated | Только если hitPart имеет Touched-обработчик и CanTouch = true. |
⚠️
Diedне срабатывает, еслиHumanoidуничтожен доHealth ≤ 0(например,:Destroy()).
Для надёжного отслеживания смерти используйтеHealthChanged:Connect(...)+ проверкуhealth ≤ 0.
3.3 Animator и анимации
Animator — неотъемлемая часть Humanoid. Доступен как humanoid.Animator.
3.3.1 Animation
- Хранит данные анимации (позы ключевых кадров).
- Создаётся через
Instance.new("Animation"). - Основное свойство:
AnimationId: string— ссылка на ассет:rbxassetid://123456789http://www.roblox.com/asset/?id=123456789rbxasset://anim.rbxl(локально, редко)
3.3.2 AnimationTrack
Результат humanoid:LoadAnimation(animation). Управляет проигрыванием.
Свойства AnimationTrack
| Свойство | Тип | Описание |
|---|---|---|
Length | number | Длительность анимации (сек). |
Looped | boolean | Повторять ли анимацию. |
Priority | Enum.AnimationPriority | Core, Movement, Action, Idle. Определяет, какая анимация «побеждает» при конфликте. |
Weight | number (0.0–1.0) | Вес смешивания (для слоёв). |
IsPlaying | boolean | Играет ли трек сейчас. |
Методы AnimationTrack
| Метод | Подпись | Описание |
|---|---|---|
:Play(fadeTime: number = 0, weight: number = 1, playbackRate: number = 1) | → void | Запуск анимации. fadeTime — наложение при смене. |
:Stop(fadeTime: number = 0) | → void | Остановка с затуханием. |
:AdjustWeight(goalWeight: number, fadeTime: number = 0) | → void | Плавное изменение веса. |
:AdjustSpeed(goalSpeed: number, fadeTime: number = 0) | → void | Изменение скорости проигрывания. |
:GetMarkerReachedSignal(name: string) | → RBXScriptSignal | Сигнал: (). Срабатывает при достижении маркера (в Keyframe). |
:GetTimePosition() | → number | Текущая позиция воспроизведения (сек). |
События AnimationTrack
| Событие | Подпись | Описание |
|---|---|---|
Stopped | () | При остановке (включая естественное завершение). |
KeyframeReached | (name: string) | При достижении keyframe с именем. |
3.4 KeyframeSequence и Keyframe
KeyframeSequence— контейнер дляKeyframe.Keyframe— содержитPose(иерархияCFrameдляAttachment).- Анимации редактируются в Animation Editor (в Studio).
- Через код можно создавать программно, но это редко и сложно.
Пример программного создания (не для продакшена):
local seq = Instance.new("KeyframeSequence")
local kf = Instance.new("Keyframe")
kf.Time = 0.5
local pose = Instance.new("Pose")
pose.Name = "Root"
pose.CFrame = CFrame.Angles(0, math.pi/4, 0)
pose.Parent = kf
kf.Parent = seq
3.5 Motor6D — суставы персонажа
- Наследует
Constraint. - Соединяет две части через
Attachment:Part0.Attachment0↔Part1.Attachment1. - Свойства:
C0,C1— локальныеCFrameдляAttachment0иAttachment1.Transform— дополнительное смещение (устаревшее, не использовать).
- При
Humanoid:BreakJoints()всеMotor6Dудаляются.
3.6 StarterPlayer и конфигурация персонажа
| Объект | Расположение | Назначение |
|---|---|---|
StarterCharacter | StarterPlayer | Копируется в Character при спавне. Если пуст — используется стандартный R6/R15. |
StarterCharacterScripts | StarterPlayer | Копируются в Character. Должны содержать LocalScript для движения и Script для серверной логики. |
StarterPlayerScripts | StarterPlayer | Копируются в PlayerScripts (дочерний Player). Для клиентской логики вне персонажа. |
CharacterAdded | Player | Событие: (character: Model). Гарантирует, что персонаж загружен. |
CharacterRemoving | Player | Событие: (character: Model). Перед уничтожением. |
⚠️ Никогда не обращайтесь к
player.Characterнапрямую — он может бытьnil. Всегда используйте:player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")
-- ...
end)
3.7 Best practices для персонажа
- Сервер не должен управлять
MoveDirection— это клиентская ответственность. Сервер может корректировать (WalkSpeed = 0при заморозке), но не задавать направление. - Анимации — клиентская логика. Сервер может запускать
AnimationTrack, но только черезRemoteEvent(или если анимация влияет на геймплей, например, каст). - Не используйте
Humanoid:MoveTo()в реальном времени — только для ИИ или автопилота. Для игрока —MoveDirection. - Сохраняйте
Humanoid.Healthтолько на сервере — клиент может подделатьHeal(). - Для кастомных персонажей:
- Используйте
R15. - Создавайте
Attachmentпо стандартным именам (Root,Waist,LeftShoulderи др.). - Вызывайте
humanoid:BuildRigFromAttachments().
- Используйте
Часть 4: Пользовательский интерфейс (UI) — GuiObject, CoreGui, безопасность и оптимизация
Эта часть описывает графическую подсистему интерфейса Roblox, включая иерархию GuiObject, ограничения рендеринга, безопасность и рекомендации по производительности.
Акцент — на корректное использование StarterGui, изоляцию клиента/сервера и избегание распространённых ошибок.
4.1 Иерархия GuiObject
Все UI-элементы наследуются от GuiObject → Instance:
Instance
└── GuiObject
├── GuiButton
│ ├── TextButton
│ └── ImageButton
├── GuiLabel
│ └── TextLabel
├── Frame
│ └── ScrollingFrame
├── ImageLabel
├── TextBox
├── CanvasGroup
└── BillboardGui (в 3D-мире)
└── WorldBillboardGui
⚠️ UI существует только на клиенте. Сервер не имеет доступа к
PlayerGui,CoreGui,StarterGui(кромеStarterGuiкак шаблона).
4.2 Базовый класс GuiObject
4.2.1 Свойства GuiObject
| Свойство | Тип | Доступ | Описание |
|---|---|---|---|
Size | UDim2 | RW | Размер: UDim2.new(scaleX, offsetX, scaleY, offsetY). scale — доля родителя (0.0–1.0), offset — пиксели. |
Position | UDim2 | RW | Позиция относительно родителя (левый верхний угол). |
AnchorPoint | Vector2 | RW | Точка привязки (0,0 = левый верх, 0.5,0.5 = центр). Смещает Position относительно неё. |
AbsoluteSize | Vector2 | RO | Фактический размер в пикселях. |
AbsolutePosition | Vector2 | RO | Фактическая позиция в пикселях. |
Visible | boolean | RW | Отображение элемента и всех потомков. |
Active | boolean | RW | Обрабатывать ли ввод (нажатия, наведение). Если false — клики проходят «сквозь». |
Selectable | boolean | RW | Можно ли выбрать элемент (для TextBox, ImageButton). |
BackgroundColor3 | Color3 | RW | Цвет фона. |
BackgroundTransparency | number (0.0–1.0) | RW | Прозрачность фона. 1 = полностью прозрачный. |
BorderColor3 | Color3 | RW | Цвет границы. |
BorderSizePixel | number | RW | Толщина границы (в пикселях). 0 = без границы. |
ClipsDescendants | boolean | RW | Обрезать ли потомков за пределами AbsoluteSize. |
ZIndex | number (1–10) | RW | Глубина отрисовки (чем выше — тем «ближе»). Максимум 10. |
LayoutOrder | number | RW | Порядок для UIListLayout, UIGridLayout. |
AutoLocalize | boolean | RW | Использовать ли систему локализации (LocalizationService). |
4.2.2 Методы GuiObject
| Метод | Подпись | Описание |
|---|---|---|
:TweenSize(size: UDim2, easingDirection: Enum.EasingDirection, easingStyle: Enum.EasingStyle, time: number, override: boolean, callback: function) | → Tween | Плавное изменение Size. |
:TweenPosition(position: UDim2, easingDirection: Enum.EasingDirection, easingStyle: Enum.EasingStyle, time: number, override: boolean, callback: function) | → Tween | Плавное изменение Position. |
:TweenSizeAndPosition(size, position, ...) | → Tween | Комбинированная анимация. |
:GetChildren() | → {Instance} | Только прямые потомки (не рекурсивно). |
:FindFirstChild(name, recursive) | → Instance | nil | Аналогично Instance. |
4.2.3 События GuiObject
| Событие | Подпись | Описание |
|---|---|---|
InputBegan | (input: InputObject, gameProcessed: boolean) | Нажатие внутри элемента. gameProcessed = true, если ввод перехвачен (например, движение мыши). |
InputEnded | (input: InputObject, gameProcessed: boolean) | Отпускание. |
MouseButton1Click | () | Клик ЛКМ (вызывается только если MouseButton1Down → MouseButton1Up без движения). |
MouseButton1Down | () | Нажатие ЛКМ. |
MouseButton1Up | () | Отпускание ЛКМ. |
MouseEnter | () | Наведение курсора. |
MouseLeave | () | Уход курсора. |
MouseMoved | (x: number, y: number) | Перемещение мыши над элементом. |
MouseWheelBackward | () | Колёсико вниз. |
MouseWheelForward | () | Колёсико вверх. |
⚠️
MouseButton1Clickне срабатывает, если курсор вышел за пределы элемента доUp. Для надёжного клика используйтеInputBegan+InputEndedс проверкойUserInputType == Enum.UserInputType.MouseButton1.
4.3 Основные UI-элементы
Frame
- Контейнер для группировки. По умолчанию
BackgroundTransparency = 1,Active = false. - Используется с
UIListLayout,UIPadding,UIScale.
TextLabel / TextButton
| Свойство | Тип | Описание |
|---|---|---|
Text | string | Текст (поддерживает \n). |
TextSize | number | Размер шрифта (в пикселях). Максимум 100. |
Font | Enum.Font | Legacy, SourceSans, Gotham, RobotoMono и др. |
TextColor3 | Color3 | Цвет текста. |
TextWrapped | boolean | Перенос по словам. |
TextXAlignment / TextYAlignment | Enum.TextXAlignment / Y | Left, Center, Right / Top, Center, Bottom. |
RichText | boolean | Включить форматирование: <b>, <i>, <font color="#FF0000">. |
TextScaled | boolean | Автомасштабирование текста под размер фрейма. |
⚠️
RichTextне поддерживает изображения (<img>), только стили.
ImageButton / ImageLabel
| Свойство | Тип | Описание |
|---|---|---|
Image | string | rbxassetid://, http://, rbxasset://. |
ImageColor3 | Color3 | Цвет наложения (умножение). |
ImageTransparency | number | Прозрачность изображения. |
ScaleType | Enum.ScaleType | Stretch, Crop, Fit, Slice (для 9-slice). |
SliceCenter | Rect | Область для ScaleType.Slice (в пикселях исходного изображения). |
ImageRectOffset / ImageRectSize | Vector2 | Для спрайт-листов (смещение и размер кадра). |
ScrollingFrame
- Прокручиваемый контейнер.
- Требует
CanvasSize: UDim2— размер содержимого (в пикселях:UDim2.new(0, width, 0, height)). - Автоматически добавляет скроллбары при
ScrollBarThickness > 0. - События:
CanvasPositionChanged,VerticalScrollBarInset,HorizontalScrollBarInset.
TextBox
| Свойство | Тип | Описание |
|---|---|---|
Text | string | Текущий ввод. |
ClearTextOnFocus | boolean | Очищать при фокусе. |
PlaceholderText | string | Подсказка (серый текст). |
TextColor3 / PlaceholderColor3 | Color3 | Цвет текста / подсказки. |
TextEditable | boolean | Можно ли редактировать. |
MaxLength | number | Ограничение длины (0 = без ограничения). |
MultiLine | boolean | Многострочный ввод. |
⚠️
Textне реплицируется серверу. Для валидации —RemoteEvent.
4.4 UI-ограничения и компоновка
| Объект | Назначение |
|---|---|
UIListLayout | Вертикальное/горизонтальное выравнивание потомков (FillDirection, HorizontalAlignment, VerticalAlignment, Padding, SortOrder). |
UIPadding | Отступы внутри фрейма (PaddingLeft, Top, Right, Bottom). |
UIScale | Масштабирование всего поддерева (Scale). |
UIAspectRatioConstraint | Фиксированное соотношение сторон (AspectRatio). |
UITextSizeConstraint | Ограничение размера шрифта (MinTextSize, MaxTextSize). |
UICorner | Скругление углов (CornerRadius). |
UIStroke | Обводка (Color, Thickness, Transparency). |
Пример: центрированный фрейм
local frame = Instance.new("Frame")
frame.Size = UDim2.new(0, 200, 0, 100)
frame.AnchorPoint = Vector2.new(0.5, 0.5)
frame.Position = UDim2.new(0.5, 0, 0.5, 0)
4.5 Иерархия UI в игре
PlayerGui (в Player)
├── ScreenGui (основной слой)
│ ├── Frame (меню)
│ └── TextLabel (очки)
├── BillboardGui (в 3D-мире)
└── StarterGui (только шаблон, копируется в ScreenGui при спавне)
StarterGui— шаблон. При входе игрока его содержимое копируется вPlayer.PlayerGui.CoreGui— системные элементы (чёрный экран, меню паузы, чат).- Доступ только для чтения (
game:GetService("CoreGui")). - Можно скрыть:
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, false). - Запрещено удалять или модифицировать напрямую.
- Доступ только для чтения (
4.6 Безопасность и валидация
-
UI — клиентская зона ответственности
- Сервер не должен доверять данным из
TextBox.Text,ImageButton.Image, и т.п. - Все действия (покупки, ввод команд) должны передаваться через
RemoteEventс валидацией на сервере.
- Сервер не должен доверять данным из
-
Ограничения ввода
TextBoxне защищает от XSS —RichTextне исполняет JS, но можно обмануть визуально.- Для фильтрации чата используйте
Chat:FilterStringAsync().
-
Скрытие UI от читеров
- Не храните конфиденциальные данные (
Health,Gold) вTextLabel.Text. - Используйте
:SetAttribute()+RemoteEventдля обновления.
- Не храните конфиденциальные данные (
4.7 Производительность UI
| Проблема | Решение |
|---|---|
Слишком много TextLabel | Объединяйте текст в один элемент. |
Частые :TweenPosition() | Используйте TweenService:Create() один раз, затем :Play(). |
Сложные ScrollingFrame с 1000+ элементами | Виртуализируйте: отрисовывайте только видимые. |
MouseMoved на каждом фрейме | Дебаунсинг через tick() или RunService.Heartbeat. |
RichText с большим объёмом | Избегайте — рендеринг очень дорогой. |
⚠️ Не используйте
wait()в обработчикахInputBegan— блокировка потока ввода вызывает лаги.
Часть 5: Сетевое взаимодействие — RemoteEvent, RemoteFunction, валидация и безопасность
Эта часть описывает механизмы межконтекстного взаимодействия в Roblox: клиент ↔ сервер, а также best practices по безопасности, производительности и отказоустойчивости.
Акцент — на корректное использование RemoteEvent/RemoteFunction, защиту от читерства и обработку ошибок.
5.1 Общая архитектура взаимодействия
- Клиент (
LocalScript) может:- Отправлять данные на сервер через
RemoteEvent:FireServer(...) - Вызывать функции на сервере через
RemoteFunction:InvokeServer(...) - Получать данные от сервера через
RemoteEvent.OnClientEvent:Connect(...)
- Отправлять данные на сервер через
- Сервер (
Script) может:- Рассылать данные клиентам через
RemoteEvent:FireAllClients(...),:FireClient(player, ...) - Отвечать на вызовы через
RemoteFunction.OnServerInvoke = function(...) → return ... end - Вызывать функции на клиенте через
RemoteFunction:InvokeClient(player, ...)
- Рассылать данные клиентам через
⚠️ Запрещено:
- Пытаться вызвать
FireServerизScript(ошибка времени выполнения)- Вызывать
FireClientбез проверкиplayer:FindFirstChild("PlayerGui")(клиент может быть не загружен)- Передавать
Instance,userdata,function,thread— только сериализуемые типы (см. 5.4)
5.2 RemoteEvent — односторонняя асинхронная передача
5.2.1 Создание и размещение
- Создаётся в
ReplicatedStorage(рекомендуется) илиServerStorage(если клиент не должен знать о его существовании до момента передачи). - Пример иерархии:
ReplicatedStorage
└── Remotes
├── PlayerAction
├── ChatMessage
└── ToolUse
5.2.2 Методы и события
| Контекст | Метод / Событие | Подпись | Описание |
|---|---|---|---|
| Клиент | :FireServer(...) | → void | Отправляет аргументы на сервер. |
| Клиент | .OnClientEvent | RBXScriptSignal | Сигнал: (...). Получение данных от сервера. |
| Сервер | :FireAllClients(...) | → void | Рассылка всем подключённым игрокам. |
| Сервер | :FireClient(player: Player, ...) | → void | Отправка конкретному игроку. |
| Сервер | .OnServerEvent | RBXScriptSignal | Сигнал: (player: Player, ...). Получение данных от клиента. |
5.2.3 Пример: безопасный выстрел
-- ServerScript (в ServerScriptService)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local ToolUse = ReplicatedStorage.Remotes.ToolUse
local COOLDOWN = 0.5
local playerLastUse = {}
ToolUse.OnServerEvent:Connect(function(player, targetPos)
-- 1. Валидация игрока
if not player or not player.Character then return end
local humanoid = player.Character:FindFirstChild("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
-- 2. Античит: проверка расстояния
local root = player.Character:FindFirstChild("HumanoidRootPart")
if not root then return end
local distance = (root.Position - targetPos).Magnitude
if distance > 300 then return end -- слишком далеко
-- 3. Анти-спам: cooldown
local now = tick()
if playerLastUse[player] and (now - playerLastUse[player]) < COOLDOWN then
return
end
playerLastUse[player] = now
-- 4. Выполнение действия (на сервере!)
-- spawn bullet, apply damage, etc.
end)
-- LocalScript (в StarterPlayerScripts)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local ToolUse = ReplicatedStorage.Remotes.ToolUse
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
local mouse = game.Players.LocalPlayer:GetMouse()
ToolUse:FireServer(mouse.Hit.Position)
end
end)
5.3 RemoteFunction — двусторонний синхронный вызов
5.3.1 Особенности
- Блокирует поток до получения ответа (yield-функция).
- Максимальное время ожидания — 3 секунды (если клиент не ответит —
nil). - Не рекомендуется для частых вызовов (например, каждый кадр).
5.3.2 Методы и события
| Контекст | Метод / Событие | Подпись | Описание |
|---|---|---|---|
| Клиент | :InvokeServer(...) | → ... | Блокирует, возвращает результат с сервера. |
| Сервер | .OnServerInvoke | function(player: Player, ...) → ... | Обработчик вызова от клиента. |
| Сервер | :InvokeClient(player: Player, ...) | → any | nil | Блокирует, возвращает результат от клиента (или nil при таймауте). |
| Клиент | .OnClientInvoke | function(...) → ... | Обработчик вызова от сервера. |
5.3.3 Пример: запрос данных профиля
-- Server (Script)
local GetProfile = ReplicatedStorage.Remotes.GetProfile
GetProfile.OnServerInvoke = function(player)
local success, data = pcall(function()
return {
level = 5,
gold = 1250,
equipped = {"Sword", "Shield"}
}
end)
return success and data or nil
end
-- Client (LocalScript)
local profile = GetProfile:InvokeServer()
if profile then
print("Уровень:", profile.level)
else
warn("Не удалось загрузить профиль")
end
⚠️
InvokeClientне следует использовать для критичных операций — клиент может намеренно не отвечать.
5.4 Поддерживаемые типы данных
Roblox сериализует только следующие типы:
| Тип | Поддержка | Примечания |
|---|---|---|
nil | ✅ | Передаётся как nil. |
boolean | ✅ | |
number | ✅ | Только double (64-bit float). Целые > 2⁵³ теряют точность. |
string | ✅ | UTF-8, до 32 KB на строку. |
Vector3, CFrame, UDim, UDim2, Ray, Color3, BrickColor, Rect | ✅ | Встроенные типы Roblox. |
Enum.* | ✅ | Передаётся как (Enum, value), восстанавливается автоматически. |
Instance | ❌ | Заменяется на nil. |
function, thread, userdata | ❌ | Заменяются на nil. |
table | ✅ (ограниченно) | Только массивы и словари с ключами-строками/числами. Циклические ссылки — обрезаются. Метатаблицы игнорируются. |
⚠️ Передача таблицы с 10 000 элементов может вызвать превышение лимита пакета (1 MB). Используйте
HttpService:JSONEncode()+ сжатие, если нужно много данных.
5.5 Лимиты и ограничения
| Параметр | Значение | Последствия превышения |
|---|---|---|
| Размер пакета | ≤ 1 MB | Ошибка: Remote function/event payload too large |
| Частота вызовов | ≤ 200 / сек на игрока | Пакеты дропаются, RemoteEvent — lossy. |
| Глубина таблицы | ≤ 100 уровней | Обрезается. |
| Длина строки | ≤ 32 767 символов | Обрезается. |
| Количество аргументов | ≤ 200 | Ошибка при вызове. |
Совет: для потоковой передачи (например, прогресс загрузки) используйте несколько
FireServer()с небольшими порциями.
5.6 Best practices
-
Валидация на сервере — обязательно
- Проверяйте:
player ~= nilplayer.Character ~= nilplayer == game.Players:GetPlayerFromCharacter(script.Parent)(для Tool)- Диапазоны (
WalkSpeed ≤ 100,Health ≥ 0) - Временные окна (cooldown, double-spend)
- Проверяйте:
-
Используйте паттерн «запрос-подтверждение» для критичных действий
-- Клиент: FireServer("buyItem", itemId)
-- Сервер: проверяет баланс → FireClient(player, "confirmBuy", itemId, price)
-- Клиент: показывает диалог → FireServer("confirmBuy", itemId)
-- Сервер: списывает деньги -
Не передавайте состояние UI на сервер
- Например, не отправляйте
frame.Visible— сервер должен сам решать, что открыто.
- Например, не отправляйте
-
Избегайте
FireAllClientsдля частых обновлений- Используйте
BindableEventдля локальной синхронизации внутри клиента.
- Используйте
-
Обрабатывайте ошибки
- Оборачивайте
pcallвOnServerEvent,OnServerInvoke. - Логируйте подозрительные вызовы (
warn("Player", player.Name, "sent invalid data")).
- Оборачивайте
Часть 6: Сохранение данных — DataStoreService, OrderedDataStore, квоты и отказоустойчивость
Эта часть описывает механизмы постоянного хранения данных игроков и игры в Roblox, включая типы DataStore, ограничения, обработку ошибок и промышленные паттерны.
Акцент — на надёжность, соответствие лимитам платформы и защиту от потери данных.
6.1 Общая архитектура DataStore
- Все операции выполняются только на сервере (
Script). - Клиент не имеет доступа к
DataStoreService(попытка вызова → ошибка). - Данные хранятся в облаке Roblox, реплицируются между серверами.
- Нет гарантии мгновенной синхронизации между разными
DataModel(серверами).
6.1.1 Глобальные сервисы
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
6.1.2 Типы DataStore
| Тип | Создание | Назначение | Ограничения |
|---|---|---|---|
| Standard DataStore | DataStoreService:GetDataStore("Name") | Хранение таблиц (до 4 MB на ключ) | 64 ключа/мин/сервер, 4000 чтений/мин/сервер |
| OrderedDataStore | DataStoreService:GetOrderedDataStore("Name") | Ранжированные данные (числа, до 10⁹) | Только number, string (ключ); 64 ключа/мин/сервер |
| GlobalDataStore | DataStoreService:GetGlobalDataStore() | Устаревший. Не использовать. | Заменён Standard. |
| ProfileService (не встроенный) | Через require(ReplicatedStorage.ProfileService) | Промышленный менеджер профилей (рекомендуется) | Сторонняя библиотека от DevForum |
⚠️ Standard и Ordered — изолированы:
GetOrderedDataStore("Scores")≠GetDataStore("Scores").
6.2 Standard DataStore
6.2.1 Основные методы
| Метод | Подпись | Возвращает | Примечания |
|---|---|---|---|
:GetAsync(key: string) | → value | nil, isLoaded: boolean | Значение (любой сериализуемый тип) или nil | isLoaded = false, если ключ не существует. |
:SetAsync(key: string, value: any) | → boolean | true при успехе, false при квоте | Блокирует до подтверждения (yield). |
:UpdateAsync(key: string, transformFunction: function) | → newValue | nil | Результат функции | Атомарное обновление (см. 6.3). |
:RemoveAsync(key: string) | → value | nil | Значение до удаления | Редко используется (лучше SetAsync(nil)). |
:IncrementAsync(key: string, delta: number = 1) | → number | Новое значение | Только для числовых данных. Устаревшее — используйте UpdateAsync. |
6.2.2 Типичный цикл сохранения профиля
local DS = DataStoreService:GetDataStore("PlayerData")
Players.PlayerAdded:Connect(function(player)
local success, data = pcall(function()
return DS:GetAsync("Player_" .. player.UserId)
end)
if not success then
warn("Ошибка загрузки данных для", player.Name)
data = { level = 1, gold = 0, inventory = {} }
end
if not data then
data = { level = 1, gold = 0, inventory = {} }
end
-- Сохраняем ссылку в атрибут (для последующего сохранения)
player:SetAttribute("PlayerData", data)
player.CharacterAdded:Connect(function(char)
-- Инициализация персонажа из data
end)
end)
Players.PlayerRemoving:Connect(function(player)
local data = player:GetAttribute("PlayerData")
if data then
spawn(function()
local retries = 3
repeat
local success, err = pcall(function()
DS:SetAsync("Player_" .. player.UserId, data)
end)
if success then return end
warn("Save failed for", player.Name, "retrying...", retries)
task.wait(2)
retries -= 1
until retries <= 0
end)
end
end)
⚠️
PlayerRemovingне вызывается при краше сервера. Для защиты используйтеBindToClose(см. 6.5).
6.3 Атомарное обновление: UpdateAsync
Предотвращает гонку при одновременном изменении данных.
Пример: безопасное добавление золота
local success, newGold = pcall(function()
return DS:UpdateAsync("Player_" .. player.UserId, function(oldData)
oldData = oldData or { gold = 0 }
oldData.gold += reward
return oldData -- возвращаем новое значение
end)
end)
- Функция
transformFunctionвызывается на сервере Roblox, не в вашем скрипте. - Может быть вызвана несколько раз при конфликтах — делайте её идемпотентной.
- Не используйте замыкания — только локальные переменные.
6.4 OrderedDataStore — рейтинги и счётчики
6.4.1 Основные методы
| Метод | Подпись | Описание |
|---|---|---|
:GetValueAsync(key: string) | → number | nil | Получение значения. |
:SetValueAsync(key: string, value: number) | → void | Установка значения. |
:IncrementAsync(key: string, delta: number = 1) | → number | Атомарное увеличение. |
:GetSortedAsync(ascending: boolean, pageSize: number = 100) | → DataStorePages | Постраничная выборка. |
6.4.2 Работа с пагинацией
local ODS = DataStoreService:GetOrderedDataStore("Leaderboard")
-- Запись
ODS:IncrementAsync("Player_" .. player.UserId, 100)
-- Чтение топ-100
local pages = ODS:GetSortedAsync(false, 100) -- descending
local top = {}
for _, entry in ipairs(pages:GetCurrentPage()) do
table.insert(top, { userId = entry.key:match("_(%d+)"), score = entry.value })
end
⚠️ Ключи в
OrderedDataStore— толькоstring, значения — толькоnumber.
Для хранения имён используйте отдельныйStandard DataStore.
6.5 Обработка ошибок и отказоустойчивость
6.5.1 Типичные ошибки
| Код ошибки | Описание | Решение |
|---|---|---|
404 (KeyNotFound) | Ключ не существует | Обрабатывать как nil, инициализировать по умолчанию. |
429 (TooManyRequests) | Превышены квоты | Реализовать экспоненциальную задержку. |
500 (InternalServerError) | Ошибка на стороне Roblox | Повторить через 2–5 сек. |
DataStore key too long | Ключ > 50 символов | Хэшировать (HttpService:MD5(key)). |
6.5.2 Паттерн: экспоненциальная задержка
local function safeSetAsync(ds, key, value, maxRetries)
maxRetries = maxRetries or 5
local delay = 1
for i = 1, maxRetries do
local success, err = pcall(function()
return ds:SetAsync(key, value)
end)
if success then return true end
if not err:find("429") and not err:find("500") then
warn("Non-retryable error:", err)
return false
end
task.wait(delay)
delay = math.min(delay * 2, 60) -- max 60 sec
end
return false
end
6.5.3 BindToClose — сохранение при выключении сервера
game:BindToClose(function()
warn("Server shutting down — saving all players...")
for _, player in ipairs(Players:GetPlayers()) do
local data = player:GetAttribute("PlayerData")
if data then
DS:SetAsync("Player_" .. player.UserId, data) -- blocking
end
end
warn("All data saved. Shutting down.")
end)
⚠️
BindToCloseдаёт 30 секунд на завершение. Если не уложиться — данные потеряются.
6.6 Промышленные паттерны
6.6.1 Версионирование данных
local CURRENT_VERSION = 2
local function migrate(data)
if not data then return { version = CURRENT_VERSION, gold = 0 } end
if data.version == 1 then
-- v1 → v2: gold → resources.gold
data.resources = { gold = data.gold }
data.gold = nil
data.version = 2
end
return data
end
-- При загрузке:
local rawData = DS:GetAsync(key)
local data = migrate(rawData)
6.6.2 Throttling сохранения
- Не сохраняйте при каждом изменении.
- Используйте
Debounceилиtask.delay.
local pendingSaves = {}
local function scheduleSave(player)
if pendingSaves[player] then return end
pendingSaves[player] = true
task.delay(30, function() -- сохраняем раз в 30 сек
if player and player.Parent then
local data = player:GetAttribute("PlayerData")
if data then
safeSetAsync(DS, "Player_" .. player.UserId, data)
end
end
pendingSaves[player] = nil
end)
end
6.6.3 Резервное копирование (fallback)
- Храните копию в
leaderstats(для отладки):player.leaderstats.Gold.Value = data.gold - Используйте
HttpService:JSONEncode()+game:GetService("HttpService"):PostAsync()в свой бэкенд (если разрешено политикой).
6.7 Квоты и лимиты (2025)
| Параметр | Лимит | Единица | Комментарий |
|---|---|---|---|
| Чтение | 4000 | /мин/сервер | Standard DataStore |
| Запись | 64 | /мин/сервер | Standard DataStore |
| OrderedDataStore запись | 64 | /мин/сервер | |
| Размер значения | 4 MB | на ключ | Standard DataStore |
| Длина ключа | 50 | символов | |
| Ключей в OrderedDataStore | 1 000 000 | на игру | |
| Срок хранения | ∞ | Но данные могут быть удалены при нарушении ToS |
⚠️ Лимиты суммируются по всем DataStore в одной игре.
Для высоконагруженных игр используйте шардирование ключей:local shard = player.UserId % 10
local key = ("Shard_%d_Player_%d"):format(shard, player.UserId)
Часть 7: Звук, частицы, свет и атмосферные эффекты
Эта часть описывает визуальные и аудиальные компоненты, используемые для создания иммерсивной среды: Sound, ParticleEmitter, Fire, Smoke, Trail, Beam, а также источники света (PointLight, SpotLight, SurfaceLight) и глобальные эффекты (Sky, Lighting, Fog).
Акцент — на параметры, производительность, репликацию и ограничения платформы.
7.1 Sound — аудиосистема
7.1.1 Иерархия и размещение
Soundможет быть дочерним для:BasePart(привязан к позиции части, движется вместе с ней)Workspace(глобальный звук)PlayerGui(UI-звуки, не затрагиваются 3D-параметрами)
- Для 3D-звука обязательно наличие
BasePartв качестве родителя илиSound.Parent = workspace.
7.1.2 Свойства Sound
| Свойство | Тип | Доступ | Описание |
|---|---|---|---|
SoundId | string | RW | rbxassetid://, http://, rbxasset://. Поддерживает OGG (рекомендуется), MP3 (устаревшее). |
Volume | number (0.0–10.0) | RW | Громкость. 1.0 = номинальная. Максимум 10.0 (усиление). |
PlaybackSpeed | number (0.1–10.0) | RW | Скорость воспроизведения (и тональность). |
TimePosition | number | RW | Текущая позиция (сек). Можно устанавливать для seek. |
Looped | boolean | RW | Повторять ли звук. |
RollOffMode | Enum.RollOffMode | RW | Inverse, Linear, Constant. Управляет затуханием по расстоянию. |
RollOffMaxDistance | number | RW | Макс. дистанция слышимости (стадий). По умолчанию 100. |
RollOffMinDistance | number | RW | Мин. дистанция (внутри неё громкость постоянна). |
EmitterSize | number (0.0–1000.0) | RW | Размер источника (для пространственного звука). 0 = точечный. |
Pitch | number (0.5–2.0) | RW | Только для совместимости. Используйте PlaybackSpeed. |
Archivable | boolean | RO | Всегда true, если SoundId задан. |
7.1.3 Методы Sound
| Метод | Подпись | Контекст | Описание |
|---|---|---|---|
:Play() | → void | Both | Запуск воспроизведения. Если уже играет — перезапускает. |
:Stop() | → void | Both | Остановка. |
:Pause() | → void | Both | Пауза (можно продолжить через :Resume()). |
:Resume() | → void | Both | Продолжение после паузы. |
:IsLoaded() | → boolean | Both | Загружен ли аудиофайл в память. |
:GetInfo() | → table | Both | Метаданные: { Duration = number, SampleRate = number, Channels = number }. |
7.1.4 События Sound
| Событие | Подпись | Описание |
|---|---|---|
Played | () | После начала воспроизведения (включая seek). |
Stopped | () | При остановке (включая естественное завершение). |
LoadingFailed | (errorMessage: string) | При ошибке загрузки (SoundId недоступен). |
7.1.5 Best practices
- Используйте OGG (меньше размер, лучше совместимость).
- Для фоновой музыки —
RollOffMode = Enum.RollOffMode.Constant,RollOffMaxDistance = 1000. - Не вызывайте
:Play()чаще, чем раз в 0.1 сек — возможны артефакты. - Для UI-звуков размещайте
SoundвStarterGui→ копируется вPlayerGui.
7.2 ParticleEmitter — система частиц
7.2.1 Свойства
| Свойство | Тип | Описание |
|---|---|---|
Texture | string | rbxassetid:// изображение (рекомендуется PNG с альфа-каналом). |
EmissionDirection | Enum.NormalId | Top, Bottom, Front, Back, Left, Right, AllFaces. |
SpreadAngle | Vector2 | Угол рассеяния по X и Y (в градусах). |
VelocityInheritance | number (0.0–1.0) | Наследование скорости родителя (0 = игнорировать, 1 = полное). |
Rate | number | Частиц/сек. Максимум 1000. |
Lifetime | NumberRange | Диапазон жизни частицы (сек). Например, NumberRange.new(1, 2). |
Size | NumberSequence | Изменение размера во времени. |
Transparency | NumberSequence | Изменение прозрачности (0 = непрозрачный, 1 = прозрачный). |
Color | ColorSequence | Изменение цвета. |
Rotation | NumberRange | Начальный угол поворота. |
RotSpeed | NumberRange | Скорость вращения (град/сек). |
Acceleration | Vector3 | Постоянное ускорение (например, гравитация: Vector3.new(0, -196.2, 0)). |
Drag | number | Сопротивление среды. |
LockedToPart | boolean | Двигать эмиттер вместе с Parent (для Part). |
ZOffset | number | Смещение по Z для рендеринга (обход глубины). |
7.2.2 Производительность
- Одна сцена поддерживает до 10 000 частиц (ограничение клиента).
Rate > 500+Lifetime > 1→ 500+ частиц/сек — может вызвать лаги на слабых устройствах.- Используйте
Debris:AddItem(emitter, 5)для автоудаления.
7.3 Специализированные эффекты
| Объект | Назначение | Ключевые свойства |
|---|---|---|
Fire | Имитация пламени | Heat, Size, Color, SecondaryColor, Enabled |
Smoke | Дым | Color, Opacity, RiseVelocity, Size, Enabled |
Sparkles | Искры | Color, Enabled, SparkleColor |
Trail | След за движущейся частью | Attachment0, Attachment1, Color, Transparency, Lifetime, FaceCamera, MinLength |
Beam | Лазер/энергетический луч | Attachment0, Attachment1, Color, CurveSize0/1, Texture, Width0/1, Segments |
⚠️
TrailиBeamтребуют двухAttachment(в разныхBasePartили одной).
ПримерTrail:local trail = Instance.new("Trail")
trail.Attachment0 = tool.Grip
trail.Attachment1 = tool.Grip
trail.Lifetime = 0.5
trail.Enabled = true
trail.Parent = tool
7.4 Источники света
7.4.1 PointLight
- Точечный источник (как лампочка).
- Свойства:
Brightness(0.0–10.0)Range(стадий, до 100)ColorCastShadows(требуетGraphicsMode = 2вLighting)ShadowSoftness(0.0–1.0)
7.4.2 SpotLight
- Конусообразный свет.
- Дополнительно:
Angle(угол конуса, градусы)Face(Enum.NormalId) — направление излученияFalloff(0.0–1.0) — резкость края
7.4.3 SurfaceLight
- Плоский источник (например, подсветка панели).
- Требует
BasePartв качестве родителя. - Свойства:
Angle(угол рассеяния)Face— грань частиThickness— глубина излучения
⚠️ Все источники света реплицируются автоматически.
Максимум 64 активных источника на сцену (ограничение движка).
7.5 Глобальные настройки: Lighting
| Свойство | Тип | Описание |
|---|---|---|
Brightness | number (0.0–10.0) | Общая яркость сцены. |
ClockTime | number (0–24) | Время суток (влияет на OutdoorAmbient). |
FogStart / FogEnd | number | Дистанция начала/конца тумана. |
FogColor | Color3 | Цвет тумана. |
OutdoorAmbient | Color3 | Цвет окружения (небо → объекты). |
GlobalShadows | boolean | Тени от солнца. |
Shadows | boolean | Тени от источников света. |
Technology | Enum.Technology | Voxel, ShadowMap — влияет на качество теней. |
EnvironmentDiffuseScale / SpecularScale | number | Интенсивность отражений. |
GeographicLatitude | number (-90–90) | Влияет на положение солнца. |
7.5.1 Sky
- Дочерний
Lighting. - Свойства:
SkyboxBk,Dn,Ft,Lf,Rt,Up—rbxassetid://для граней кубмапыCelestialBodiesShown(Enum.CelestialBody) —Sun,Moon,Both,NoneStarCount— количество звёзд (до 1000)
Для динамического неба используйте
Lighting.ClockTime = math.clamp(tick() % 86400 / 3600, 0, 24).
7.6 Производительность и оптимизация
| Компонент | Совет |
|---|---|
| Звук | Используйте SoundGroup для управления громкостью категорий (музыка/эффекты). |
| Частицы | Ограничьте Rate × Lifetime ≤ 500. Используйте ParticleEmitter:Clear() при скрытии. |
| Свет | Отключайте CastShadows для некритичных источников. Избегайте Range > 50. |
| Туман | FogEnd – FogStart < 100 — предотвращает артефакты на мобильных. |
| Небо | Используйте 512×512 текстуры для Skybox (1024×1024 — только для ПК). |
Часть 8: Модули и инструменты разработки — ModuleScript, CollectionService, Binders и инфраструктурные паттерны
Эта часть описывает архитектурные средства организации кода в Roblox: модульные системы, управление жизненным циклом, отслеживание объектов по тегам, и промышленные паттерны (Janitor, Binder, Signal).
Акцент — на масштабируемость, тестируемость и поддерживаемость.
8.1 ModuleScript — основа модульной системы
8.1.1 Создание и использование
- Наследует
Instance, но не имеет визуального представления. - Возвращает любое значение через
return. - Кэшируется:
require(module)возвращает один и тот же объект при повторных вызовах.
-- ReplicatedStorage/Utils/Math.lua
local Math = {}
function Math.clamp(x, min, max)
return math.min(math.max(x, min), max)
end
return Math
-- Script
local Math = require(game.ReplicatedStorage.Utils.Math)
print(Math.clamp(15, 0, 10)) -- 10
8.1.2 Best practices
- Возвращайте таблицу интерфейса, а не отдельные функции.
- Избегайте глобальных переменных внутри модуля.
- Используйте
local module = {} ... return module— неreturn { ... }. - Для инициализации с параметрами — возвращайте конструктор:
return function(config)
local obj = {}
obj.config = config
function obj:run() ... end
return obj
end
⚠️
require()блокирует до завершения модуля. Не используйтеwait(),task.wait(),:WaitForChild()в корнеModuleScript.
8.2 CollectionService — тегирование объектов
Позволяет динамически группировать Instance по тегам (string), без иерархии.
8.2.1 Методы CollectionService
| Метод | Подпись | Описание |
|---|---|---|
:AddTag(instance: Instance, tag: string) | → void | Добавляет тег. Идемпотентен. |
:RemoveTag(instance: Instance, tag: string) | → void | Удаляет тег. |
:HasTag(instance: Instance, tag: string) | → boolean | Проверка наличия. |
:GetTagged(tag: string) | → {Instance} | Все объекты с тегом (на момент вызова). |
:GetTags(instance: Instance) | → {string} | Все теги объекта. |
8.2.2 События
| Событие | Подпись | Описание |
|---|---|---|
InstanceAdded | (tag: string, instance: Instance) | При добавлении тега. |
InstanceRemoved | (tag: string, instance: Instance) | При удалении тега. |
8.2.3 Пример: система дверей
-- Server
local CollectionService = game:GetService("CollectionService")
-- Где-то при инициализации:
CollectionService:AddTag(door1, "Door")
CollectionService:AddTag(door2, "Door")
-- Глобальный обработчик
CollectionService.InstanceAdded:Connect(function(tag, obj)
if tag == "Door" then
local proximity = Instance.new("ProximityPrompt")
proximity.ActionText = "Открыть"
proximity.Parent = obj
-- ... настройка
end
end)
⚠️ Теги не сохраняются в
.rbxl. Инициализируйте их при запуске сервера.
8.3 Binder — управление жизненным циклом компонентов
Промышленный паттерн для связывания Instance с логикой (например, Enemy → EnemyController).
Реализуется через CollectionService + события.
8.3.1 Простая реализация Binder
-- ModuleScript: Binder.lua
local CollectionService = game:GetService("CollectionService")
local Binder = {}
Binder.__index = Binder
function Binder.new(tagName: string, constructor: function)
local self = setmetatable({
Tag = tagName,
Constructor = constructor,
Instances = {},
Active = false
}, Binder)
return self
end
function Binder:Start()
if self.Active then return end
self.Active = true
-- Инициализация существующих
for _, obj in ipairs(CollectionService:GetTagged(self.Tag)) do
self:OnInstanceAdded(obj)
end
-- Подписка на изменения
self.ConnAdded = CollectionService.InstanceAdded:Connect(function(tag, obj)
if tag == self.Tag then self:OnInstanceAdded(obj) end
end)
self.ConnRemoved = CollectionService.InstanceRemoved:Connect(function(tag, obj)
if tag == self.Tag then self:OnInstanceRemoved(obj) end
end)
end
function Binder:Stop()
if not self.Active then return end
self.Active = false
self.ConnAdded:Disconnect()
self.ConnRemoved:Disconnect()
for obj in pairs(self.Instances) do
self:OnInstanceRemoved(obj)
end
end
function Binder:OnInstanceAdded(obj)
if self.Instances[obj] then return end
local ctrl = self.Constructor(obj)
self.Instances[obj] = ctrl
end
function Binder:OnInstanceRemoved(obj)
local ctrl = self.Instances[obj]
if ctrl then
if type(ctrl) == "table" and ctrl.Destroy then
ctrl:Destroy()
end
self.Instances[obj] = nil
end
end
return Binder
8.3.2 Использование
-- ServerScript
local Binder = require(ReplicatedStorage.Binder)
local EnemyController = require(ReplicatedStorage.Controllers.EnemyController)
local enemyBinder = Binder.new("Enemy", function(part)
return EnemyController.new(part)
end)
enemyBinder:Start()
game:BindToClose(function()
enemyBinder:Stop()
end)
Преимущества:
- Автоматическая привязка/отвязка при добавлении/удалении объекта
- Централизованное управление жизненным циклом
- Поддержка горячей замены (для Studio plugins)
8.4 Janitor — управление ресурсами
Класс для автоматической очистки соединений, таймеров, дочерних объектов.
8.4.1 Реализация
-- ModuleScript: Janitor.lua
local Janitor = {}
Janitor.__index = Janitor
function Janitor.new()
return setmetatable({ _tasks = {} }, Janitor)
end
function Janitor:Add(task: any, key: string?)
if typeof(task) == "RBXScriptConnection" then
if key then
self._tasks[key] = task
else
table.insert(self._tasks, task)
end
return task
elseif type(task) == "function" then
local conn
conn = game:GetService("RunService").Heartbeat:Connect(function()
conn:Disconnect()
task()
end)
if key then
self._tasks[key] = conn
else
table.insert(self._tasks, conn)
end
return conn
elseif typeof(task) == "Instance" then
table.insert(self._tasks, function() task:Destroy() end)
return task
else
error("Unsupported task type")
end
end
function Janitor:Cleanup()
for _, task in ipairs(self._tasks) do
if typeof(task) == "RBXScriptConnection" then
task:Disconnect()
elseif type(task) == "function" then
task()
end
end
for key in pairs(self._tasks) do
self._tasks[key] = nil
end
end
return Janitor
8.4.2 Пример использования
local Janitor = require(ReplicatedStorage.Janitor)
local janitor = Janitor.new()
-- Подключение события
janitor:Add(player.CharacterAdded:Connect(function(char)
-- ...
end))
-- Отложенное удаление
janitor:Add(function()
print("Очистка завершена")
end)
-- Привязка к объекту
script:WaitForChild("Janitor", 5)
if script.Janitor then
script.Janitor:Destroy()
end
script.Janitor = janitor
Используется в
PlayerRemoving,CharacterRemoving,BindToClose.
8.5 Signal — кастомные события
Встроенные RBXScriptSignal нельзя создавать вручную. Используйте Signal.
8.5.1 Реализация
-- ModuleScript: Signal.lua
local Signal = {}
Signal.__index = Signal
function Signal.new()
local self = setmetatable({
_binds = {}
}, Signal)
return self
end
function Signal:Connect(callback: function)
table.insert(self._binds, callback)
return {
Disconnect = function(this)
for i, bind in ipairs(self._binds) do
if bind == this then
table.remove(self._binds, i)
break
end
end
end
}
end
function Signal:Fire(...)
for _, bind in ipairs(self._binds) do
task.spawn(bind, ...)
end
end
function Signal:DisconnectAll()
self._binds = {}
end
return Signal
8.5.2 Применение
local Signal = require(ReplicatedStorage.Signal)
local PlayerDied = Signal.new()
-- Где-то в Humanoid:
PlayerDied:Fire(player, damageSource)
-- Где-то в UI:
local conn = PlayerDied:Connect(function(player, source)
if player == game.Players.LocalPlayer then
screenGui.DeathScreen.Visible = true
end
end)
-- При уничтожении экрана:
conn:Disconnect()
Преимущества перед
BindableEvent:
- Нет ограничений на количество слушателей
- Полный контроль над жизненным циклом
- Поддержка отладки (можно логировать
:Fire())
8.6 Тестирование: TestService и MockDataStoreService
8.6.1 TestService
- Доступен только в Studio (не в игре).
- Методы:
:Check()— завершить тест успешно:Fail(message: string)— завершить с ошибкой:Warn(message: string)— предупреждение:Start()/:Stop()— управление временем
8.6.2 MockDataStoreService (сторонний)
- Имитирует
DataStoreServiceв Studio. - Устанавливается как
Pluginили черезrequire. - Позволяет тестировать сохранение без квот.
-- Только в Studio
if game:GetService("RunService"):IsStudio() then
local MockDataStore = require(game.ReplicatedStorage.MockDataStore)
game:GetService("DataStoreService"):SetBackend(MockDataStore.new())
end
8.7 Рекомендации по архитектуре
| Проблема | Решение |
|---|---|
Спагетти-код в Script | Вынести логику в ModuleScript + Binder |
| Утечки памяти | Использовать Janitor в каждом компоненте |
| Глобальные события | Заменить BindableEvent на Signal |
| Зависимости от иерархии | Использовать CollectionService + теги |
| Сложность тестирования | Разделять Server/Client/Shared логику, использовать Mock |
Часть 9: Внешние интеграции — MarketplaceService, HttpService, GamePass, Developer Products и телепортация
Эта часть описывает механизмы взаимодействия с экосистемой Roblox: монетизация, внешние API, телепортация между серверами и загрузка ассетов.
Акцент — на безопасность, валидацию, обработку ошибок и соответствие политике платформы.
9.1 MarketplaceService — монетизация и виртуальные товары
9.1.1 Поддерживаемые типы
| Тип | Описание | Методы |
|---|---|---|
| Game Pass | Разовый доступ к функции (скин, способность) | :PlayerOwnsGamePassAsync(), :PromptPurchase() |
| Developer Product | Покупка с возвратом значений (золото, ресурсы) | :PromptProductPurchase(), :ProcessReceipt |
| Badge | Достижения | :AwardBadge(), :GetGameBadgeInfoAsync() |
9.1.2 Проверка владения Game Pass
-- Server
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local GAME_PASS_ID = 12345678
Players.PlayerAdded:Connect(function(player)
local success, owns = pcall(function()
return MarketplaceService:PlayerOwnsGamePassAsync(player.UserId, GAME_PASS_ID)
end)
if success and owns then
player:SetAttribute("HasVIP", true)
-- Открыть VIP-меню, выдать бонус и т.п.
end
end)
⚠️
PlayerOwnsGamePassAsync()блокирует (yield). Используйтеspawn()при массовой проверке.
9.1.3 Developer Products: безопасная обработка покупок
-
Настройка в Creator Dashboard
- Создать Product → указать
Price,Description,Icon. - Включить «Process Receipts» → указать
ScriptвServerScriptService.
- Создать Product → указать
-
Серверный обработчик (
ProcessReceipt)
-- Script в ServerScriptService (имя не важно)
local MarketplaceService = game:GetService("MarketplaceService")
MarketplaceService.ProcessReceipt = function(receiptInfo)
local userId = receiptInfo.PlayerId
local productId = receiptInfo.ProductId
local purchaseId = receiptInfo.PurchaseId
-- 1. Валидация: не обработан ли уже?
local dataStore = DataStoreService:GetDataStore("Purchases")
local success, alreadyProcessed = pcall(function()
return dataStore:GetAsync("Purchase_" .. purchaseId)
end)
if success and alreadyProcessed then
return Enum.ProductPurchaseDecision.PurchaseGranted
end
-- 2. Выдача товара
local player = Players:GetPlayerByUserId(userId)
if player then
if productId == 987654321 then -- "1000 Gold"
local data = player:GetAttribute("PlayerData") or {}
data.gold = (data.gold or 0) + 1000
player:SetAttribute("PlayerData", data)
end
else
-- Игрок оффлайн — сохранить в DataStore для последующей выдачи
local deferred = dataStore:GetAsync("Deferred_" .. userId) or {}
table.insert(deferred, { productId = productId, time = tick() })
dataStore:SetAsync("Deferred_" .. userId, deferred)
end
-- 3. Подтверждение обработки
pcall(function()
dataStore:SetAsync("Purchase_" .. purchaseId, true)
end)
return Enum.ProductPurchaseDecision.PurchaseGranted
end
⚠️ Критически важно:
- Использовать
PurchaseIdкак уникальный идентификатор (неProductId!).- Всегда возвращать
PurchaseGrantedпосле успешной обработки — иначе Roblox повторит вызов.- Не доверять
playerвProcessReceipt— игрок может выйти до вызова.
9.1.4 Запрос покупки с клиента
-- LocalScript
local MarketplaceService = game:GetService("MarketplaceService")
buyButton.MouseButton1Click:Connect(function()
MarketplaceService:PromptProductPurchase(
game.Players.LocalPlayer,
987654321, -- productId
false, -- включить ли бесплатную пробную версию (не для Developer Products)
"Gold Pack" -- optional context
)
end)
⚠️
PromptProductPurchase()иPromptGamePassPurchase()не возвращают результат. Успех/отказ отслеживается только черезProcessReceipt.
9.2 BadgeService — система достижений
9.2.1 Выдача бейджа
-- Server
local BadgeService = game:GetService("BadgeService")
local Players = game:GetService("Players")
local BADGE_ID = 87654321
-- Проверка, есть ли уже
local success, hasBadge = pcall(function()
return BadgeService:UserHasBadgeAsync(player.UserId, BADGE_ID)
end)
if success and not hasBadge then
local awarded = BadgeService:AwardBadge(player.UserId, BADGE_ID)
if awarded then
-- Показать уведомление
end
end
⚠️
AwardBadge()может вернутьfalse, если:
- Бейдж отключён в настройках
- Игрок уже имеет его
- Превышена квота (10 бейджей/мин/сервер)
9.3 HttpService — внешние HTTP-запросы
9.3.1 Ограничения
- Только HTTPS (
http://→ ошибка). - Доступно только на сервере.
- Требует включения в Game Settings → Security → Allow HTTP Requests.
- Лимит: 10 запросов/сек/сервер, 500 байт/сек/сервер (ограничение полосы).
- Запрещено обращение к
roblox.com,roblox.net,localhost, частным IP.
9.3.2 Методы
| Метод | Подпись | Описание |
|---|---|---|
:GetAsync(url: string, nocache: boolean = false) | → string | GET-запрос. Блокирует. |
:PostAsync(url: string, data: string, contentType: Enum.HttpContentType) | → string | POST-запрос. |
:JSONEncode(value: any) | → string | Сериализация в JSON. |
:JSONDecode(json: string) | → any | Десериализация из JSON. |
9.3.3 Пример: вебхук в Discord
-- Server
local HttpService = game:GetService("HttpService")
local function sendWebhook(playerName, action)
local payload = HttpService:JSONEncode({
content = ("**%s** %s"):format(playerName, action),
username = "Game Logger"
})
spawn(function()
local success, response = pcall(function()
return HttpService:PostAsync(
"https://discord.com/api/webhooks/...",
payload,
Enum.HttpContentType.ApplicationJson
)
end)
if not success then
warn("Webhook failed:", response)
end
end)
end
Players.PlayerAdded:Connect(function(player)
sendWebhook(player.Name, "присоединился к серверу")
end)
⚠️ Никогда не передавайте
player.UserId,IP,DeviceIdв сторонние сервисы без согласия пользователя.
9.4 TeleportService — межсерверная телепортация
9.4.1 Типы телепортации
| Метод | Назначение |
|---|---|
:Teleport(placeId, players, teleportData) | Один игрок → другой Place |
:TeleportToPrivateServer(placeId, reservedServerAccessCode, players, teleportData) | В приватный сервер |
:TeleportPartyAsync(placeId, players, teleportData) | Группа игроков вместе |
:GetPlayerTeleportData(player) | Получить teleportData при входе |
9.4.2 Передача данных (teleportData)
- Тип:
table(только сериализуемые типы, ≤ 1 KB). - Доступен через
TeleportService:GetPlayerTeleportData(player)только при первом входе в новый сервер.
-- Сервер A: отправка
TeleportService:Teleport(123456789, {player}, {
level = 5,
checkpoint = "castle"
})
-- Сервер B: приём
Players.PlayerAdded:Connect(function(player)
local data = TeleportService:GetPlayerTeleportData(player)
if data then
print("Телепортирован из другого сервера:", data.checkpoint)
end
end)
⚠️
teleportDataтеряется после первогоPlayerAdded. Сохраняйте нужное вPlayer:SetAttribute().
9.4.3 Respawn в том же сервере
player:LoadCharacter() -- без телепортации
-- или
player:Kick() -- переподключение к тому же серверу
9.5 AssetService — динамическая загрузка ассетов
9.5.1 Получение информации об ассете
local AssetService = game:GetService("AssetService")
-- Получить имя и описание
local success, info = pcall(function()
return AssetService:GetAssetInfoAsync(123456789)
end)
if success then
print(info.Name, info.Description, info.Created, info.Updated)
end
9.5.2 Поиск ассетов
local results = AssetService:SearchAsync("sword", Enum.AssetType.Mesh, 1)
for _, asset in ipairs(results.CurrentPage) do
print(asset.Id, asset.Name)
end
Используется редко — преимущественно в инструментах Studio.
9.6 Безопасность и соответствие политике
| Риск | Мера |
|---|---|
| Подделка покупок | Всегда проверяйте PurchaseId, используйте ProcessReceipt |
| Утечка данных | Не передавайте UserId в HTTP без шифрования |
| Спам телепортацией | Ограничьте частоту через Debounce |
| Нарушение ToS | Не используйте HttpService для читов, манипуляции рейтингом |
🔒 Все денежные операции должны обрабатываться только на сервере.
Клиент не должен знатьProductId,GamePassId— передавайте черезRemoteEventпо семантическим именам ("buyGoldPack").
Часть 10: Тестирование и отладка — TestService, LogService, Stats, профилировка и инструменты Studio
Эта часть описывает методы верификации корректности и производительности Roblox-проектов: юнит-тестирование, логирование, сбор метрик, профилирование и разработка плагинов.
Акцент — на воспроизводимость, автоматизацию и соответствие промышленным стандартам.
10.1 TestService — встроенная система тестирования
Доступна только в Roblox Studio, не существует в запущенной игре.
10.1.1 Основные методы
| Метод | Подпись | Описание |
|---|---|---|
:Check() | → void | Успешное завершение теста. Останавливает выполнение сценария. |
:Fail(message: string) | → void | Провал теста с сообщением. Останавливает выполнение. |
:Warn(message: string) | → void | Предупреждение (не останавливает). |
:Start() | → void | Запуск таймера (для замера времени выполнения). |
:Stop() | → number | Остановка таймера, возврат времени в секундах. |
10.1.2 Структура тестового скрипта
Размещается в ServerScriptService или TestService (специальная папка, поддерживаемая плагинами).
-- Test_MathUtils.lua
local TestService = game:GetService("TestService")
local Math = require(game.ReplicatedStorage.Utils.Math)
-- Тест 1: clamp
do
local result = Math.clamp(15, 0, 10)
if result ~= 10 then
TestService:Fail(("clamp(15,0,10) = %d, expected 10"):format(result))
end
end
-- Тест 2: производительность
do
TestService:Start()
for i = 1, 100000 do
Math.clamp(i % 20, 5, 15)
end
local elapsed = TestService:Stop()
if elapsed > 0.1 then
TestService:Warn(("clamp 100k calls: %.3f sec (slow)"):format(elapsed))
end
end
TestService:Check() -- все тесты пройдены
⚠️ Тесты не запускаются автоматически. Используйте плагин TestEZ или Rojo + selene + TestEZ для CI.
10.2 LogService — централизованное логирование
10.2.1 Методы и уровни
| Метод | Уровень | Описание |
|---|---|---|
:GetLogHistory() | — | → {LogInfo} — последние 100 сообщений. |
message | Message | Информационное сообщение. |
warning | Warning | Предупреждение. |
error | Error | Ошибка (не прерывает выполнение). |
Каждое сообщение — таблица LogInfo:
{
Message = "текст",
Timestamp = DateTime,
Level = Enum.MessageType.Message,
MessageId = "string"
}
10.2.2 Пример: логгер с метаданными
-- ModuleScript: Logger.lua
local LogService = game:GetService("LogService")
local Logger = {}
function Logger.info(tag: string, ...)
LogService:Message(("[INFO][%s] %s"):format(tag, table.concat({...}, " ")))
end
function Logger.warn(tag: string, ...)
LogService:Warning(("[WARN][%s] %s"):format(tag, table.concat({...}, " ")))
end
function Logger.error(tag: string, ...)
LogService:Error(("[ERROR][%s] %s"):format(tag, table.concat({...}, " ")))
end
-- Экспорт для внешнего анализа
function Logger.getHistory()
return LogService:GetLogHistory()
end
return Logger
Используйте теги (
"DataStore","Network") для фильтрации в Studio → View → Output.
10.3 Stats — сбор системных метрик
Доступен через game:GetService("Stats").
Возвращает древовидную структуру Int64, Double, Object (для группировки).
10.3.1 Ключевые метрики
| Путь | Тип | Описание |
|---|---|---|
Network.IncomingKB | Double | Входящий трафик (KB) |
Network.OutgoingKB | Double | Исходящий трафик (KB) |
Physics.StepTime | Double | Время шага физики (мс) |
Render.Fps | Double | Кадров/сек (только клиент) |
Memory.Physics | Int64 | Память физики (байт) |
Memory.Script | Int64 | Память скриптов (байт) |
TaskScheduler.Threads | Int64 | Количество потоков сервера |
10.3.2 Чтение метрик
local Stats = game:GetService("Stats")
local function getMetric(path: string)
local obj = Stats
for part in path:gmatch("[^.]+") do
obj = obj[part]
if not obj then return nil end
end
return typeof(obj) == "Double" and obj.Value or obj.Count
end
print("FPS:", getMetric("Render.Fps"))
print("Physics Step:", getMetric("Physics.StepTime"), "ms")
⚠️ Некоторые метрики (
Render.*) доступны только на клиенте.
10.4 Профилировка производительности
10.4.1 Встроенный профайлер Roblox Studio
- Запустите игру в Studio.
- View → MicroProfiler.
- Нажмите Record, выполните сценарий, остановите запись.
- Анализируйте:
Script— время выполнения Lua (включая yield)Physics— расчёты коллизий, constraintsRender— рендеринг геометрии, UI, частицNetwork— сериализация, отправка пакетов
10.4.2 Программная профилировка
-- Замер времени выполнения участка кода
local start = os.clock()
-- ... код ...
local elapsed = os.clock() - start
print(("Выполнено за %.4f сек"):format(elapsed))
os.clock()— высокоточный таймер (микросекунды), но не учитывает время ожидания (wait,:WaitForChild).
10.5 Debug — низкоуровневая отладка
10.5.1 Методы
| Метод | Описание |
|---|---|
Debug.profilebegin(name: string) | Начало именованного профилировочного блока (отображается в MicroProfiler) |
Debug.profileend() | Конец блока |
Debug.setmemorycategory(category: string) | Установка категории памяти для последующих аллокаций (для анализа утечек) |
10.5.2 Пример
Debug.profilebegin("LoadPlayerData")
local data = DS:GetAsync("Player_" .. player.UserId)
Debug.profileend()
Debug.setmemorycategory("PlayerData")
local cache = {} -- все аллокации пойдут в категорию "PlayerData"
Категории памяти видны в View → Memory → Allocations by Category.
10.6 Studio Plugin API — автоматизация разработки
Плагины работают только в Studio, имеют доступ к редактору.
10.6.1 Основные сервисы
| Сервис | Назначение |
|---|---|
Plugin | Управление жизненным циклом плагина |
PluginGui | Создание окон в Studio |
ChangeHistoryService | Отмена/повтор действий (Ctrl+Z) |
Selection | Получение/установка выделенных объектов |
StarterGui, Workspace, и др. | Прямой доступ к иерархии |
10.6.2 Пример: плагин для стандартизации Part
-- Plugin.lua
local Plugin = script:FindFirstAncestorWhichIsA("Plugin")
local Selection = game:GetService("Selection")
local button = Plugin:CreateToolbar("Tools"):CreateButton(
"FixPart",
"Привести Part к стандарту",
"rbxassetid://123456789"
)
button.Click:Connect(function()
for _, obj in ipairs(Selection:Get()) do
if obj:IsA("BasePart") then
obj.Material = Enum.Material.Plastic
obj.BrickColor = BrickColor.new("Bright blue")
obj.Anchored = true
obj.CanCollide = true
end
end
end)
Плагины не публикуются в игру — только для разработки.
10.7 Best practices для отладки
| Сценарий | Решение |
|---|---|
| Сложно воспроизвести баг | Используйте LogService + :GetLogHistory() при PlayerRemoving |
| Подозрение на утечку памяти | Debug.setmemorycategory() + анализ в Memory |
| Низкий FPS на клиенте | MicroProfiler → фильтр Render / Script |
| Задержки синхронизации | Stats.Network.OutgoingKB, ReplicationLag (в NetworkServerStats) |
| Ошибки DataStore | Логируйте pcall + err в LogService:error() |
🔧 Для CI/CD используйте:
- Rojo — синхронизация кода между Studio и Git
- Selene — линтер Lua
- TestEZ — фреймворк для юнит-тестов
- Tarmac — форматирование кода
Часть 11: Производительность и оптимизация — углублённый анализ движка Roblox
Эта часть охватывает системные аспекты производительности: профилировку, лимиты, оптимизацию физики, рендеринга, сети и памяти.
Акцент — на измеримые метрики, количественные ограничения и проверенные методы повышения стабильности.
11.1 Архитектура движка и частоты обновления
| Подсистема | Частота | Контекст | Описание |
|---|---|---|---|
| Render | 60–240 Гц | Клиент | Зависит от монитора и RunService:IsHeartbeatStepping(). Не регулируется скриптами. |
| Heartbeat | ~60 Гц | Клиент | RunService.Heartbeat:Connect(...) — для UI, анимаций, предиктивного рендеринга. |
| RenderStepped | ~60 Гц | Клиент | RunService.RenderStepped:Connect(...) — для привязки к кадру (устаревшее, предпочтительно Heartbeat). |
| Stepped | 240 Гц | Сервер | RunService.Stepped:Connect(function(time, deltaTime) ...) — физика, перемещение. |
| Physics | 240 Гц | Сервер/Клиент | Внутренний шаг симуляции (фиксированный). Не зависит от FPS. |
| Network | 20–30 Гц | Сервер→Клиент | Частота репликации состояния (адаптивная, зависит от NetworkServerStats.OutgoingReplicationLag). |
⚠️
Stepped≠Heartbeat:
Stepped— серверный, синхронизирован с физикой (240 Гц).Heartbeat— клиентский, синхронизирован с рендером (~60 Гц).
Никогда не используйтеHeartbeatдля физики или перемещения персонажа.
11.2 Профилировка: MicroProfiler и метрики
11.2.1 Ключевые слои в MicroProfiler
| Слой | Норма (мс/кадр) | Критично | Рекомендации |
|---|---|---|---|
Script | ≤ 1.0 | > 3.0 | Оптимизировать hot path, избегать :GetDescendants(), ipairs вместо pairs |
Physics | ≤ 2.0 | > 5.0 | Уменьшить CanCollide, использовать CollisionFidelity = Hull, ограничить Constraint |
Render.Geometry | ≤ 2.0 | > 4.0 | LOD, Transparency = 1 для невидимых частей, :ClearAllChildren() вместо :Destroy() по одной |
Render.UI | ≤ 0.5 | > 1.0 | Избегать RichText, виртуализировать ScrollingFrame, не обновлять TextLabel.Text каждый кадр |
Network.Encode | ≤ 0.3 | > 1.0 | Сократить количество RemoteEvent, объединять данные в таблицы |
11.2.2 Программный доступ к лагам
local RunService = game:GetService("RunService")
-- Только на сервере
if RunService:IsServer() then
RunService.Heartbeat:Connect(function()
local stats = game:GetService("Stats")
local lag = stats.Network:FindFirstChild("OutgoingReplicationLag")
if lag and lag.Value > 0.1 then -- 100 мс
warn("Высокая сетевая задержка:", lag.Value)
end
end)
end
11.3 Физика: оптимизация и ограничения
11.3.1 Иерархия ограничений (Constraint)
Constraint
├── AlignOrientation
├── AlignPosition
├── BallSocketConstraint
├── CylindricalConstraint
├── HingeConstraint
├── LineForce
├── PrismaticConstraint
├── RopeConstraint
├── SpringConstraint
├── RodConstraint
├── SlipConstraint
├── TwistConstraint
└── WeldConstraint
11.3.2 Рекомендации
- Максимум 100 активных
Constraintна сцену (иначе падаетPhysics.StepTime). WeldConstraintбыстрееMotor6D(в 2–3 раза). Для статичных соединений — использоватьWeldConstraint.RopeConstraintиSpringConstraint— самые дорогие. Заменять наAttachment+BodyPositionпри возможности.- Отключать
Active = falseу неиспользуемыхConstraint.
11.3.3 PhysicsService — управление коллайдерами
| Метод | Описание |
|---|---|
:GetCollisionGroups() | Список групп коллизий. |
:CreateCollisionGroup(name: string) | Создание группы. |
:CollisionGroupSetCollidable(groupA, groupB, collidable: boolean) | Настройка взаимодействия групп. |
Пример: игрок не сталкивается с декором:
PhysicsService:CreateCollisionGroup("Player")
PhysicsService:CreateCollisionGroup("Decor")
PhysicsService:CollisionGroupSetCollidable("Player", "Decor", false)
character:SetAttribute("CollisionGroup", "Player")
decorPart.CollisionGroup = "Decor"
⚠️
CollisionGroupработает только дляBasePart, не дляModel.
11.4 Рендеринг: геометрия, UI, частицы
11.4.1 Геометрия
| Параметр | Оптимизация |
|---|---|
| Количество частей | ≤ 10 000 на сцену (рекомендовано ≤ 5 000) |
| MeshPart | Использовать CollisionFidelity = Hull (не Precise) |
| Текстуры | 512×512 для мобильных, 1024×1024 — только для ПК; общая сумма VRAM ≤ 512 MB |
| Прозрачность | Избегать Transparency ∈ (0, 1) — только 0 или 1. Промежуточные значения → alpha sorting → лаги. |
11.4.2 UI
| Проблема | Решение |
|---|---|
TextLabel с TextWrapped = true | Кэшировать размер через TextService:GetTextSize() |
Частые :TweenPosition() | Использовать TweenService:Create() один раз, затем :Play() |
ScrollingFrame с 1000+ элементами | Виртуализация: отрисовывать только ViewportSize / ItemSize + 2 элементов |
11.4.3 Частицы
| Компонент | Лимит | Совет |
|---|---|---|
ParticleEmitter | ≤ 10 активных | Останавливать через Enabled = false, не удалять |
| Общее число частиц | ≤ 10 000 | Rate × Lifetime ≤ 500 на эмиттер |
Trail, Beam | ≤ 50 | Использовать MinLength, FaceCamera = false для оптимизации |
11.5 Сеть: репликация и лимиты
11.5.1 Что реплицируется автоматически
| Тип | Примеры | Частота |
|---|---|---|
Свойства Instance | CFrame, Velocity, Transparency, Color у BasePart | Адаптивно (до 30 Гц) |
Value-объекты | StringValue, NumberValue, BoolValue | При изменении |
Sound | TimePosition, Playing | При изменении |
Humanoid | Health, MoveDirection, Jump | До 20 Гц |
11.5.2 Что не реплицируется
LocalScript,PlayerGui,CoreGui- Свойства с
ReadOnlyна клиенте - Пользовательские атрибуты, установленные через
:SetAttribute()на клиенте - Изменения в
ServerStorage,ServerScriptService
11.5.3 Оптимизация трафика
- Объединять данные:
-- Плохо:
Remote:FireServer("move", x)
Remote:FireServer("move", y)
Remote:FireServer("move", z)
-- Хорошо:
Remote:FireServer("move", Vector3.new(x, y, z)) - Использовать
DebounceдляMoveDirection:if (newDir - lastDir).Magnitude > 0.1 then
Remote:FireServer("dir", newDir)
lastDir = newDir
end - Для частых обновлений (например, HP) — использовать
NumberValueвместоRemoteEvent.
11.6 Память: анализ и утечки
11.6.1 Мониторинг
- View → Memory в Studio:
- Allocations — новые объекты за последние 5 сек
- Instances — количество
Instanceпо классам - Lua Memory — использование кучи Lua
11.6.2 Признаки утечек
| Симптом | Причина | Решение |
|---|---|---|
Instance count растёт линейно | Не вызван :Destroy() | Использовать Janitor |
Lua Memory > 50 MB | Замыкания, глобальные таблицы | Избегать global = {}, использовать local |
Debris не удаляет объекты | Циклические ссылки | Не сохранять Instance в замыканиях |
11.6.3 Debris — отложенное удаление
local Debris = game:GetService("Debris")
-- Автоудаление через 5 сек
Debris:AddItem(part, 5)
-- Удаление при изменении родителя (защита от утечек)
part.AncestryChanged:Connect(function(_, parent)
if not parent then
Debris:AddItem(part, 0.1)
end
end)
⚠️
Debrisработает только дляInstance, не для соединений или таймеров.
11.7 RunService — управление выполнением
| Свойство/Метод | Контекст | Назначение |
|---|---|---|
:IsServer() / :IsClient() | Both | Определение среды |
:IsStudio() | Both | Проверка запуска в Studio |
:IsRunning() | Both | Игра запущена (не в Studio idle) |
Heartbeat | Client | Для UI, анимаций, предиктивных эффектов |
RenderStepped | Client | Устаревшее. Используйте Heartbeat. |
Stepped | Server | Для физики, перемещения, синхронных расчётов |
BindToRenderStep(name, priority, func) | Client | Вставка в pipeline рендеринга (редко) |
⚠️ Никогда не используйте
while true do wait() ... end— заменяйте на события (Stepped,Heartbeat).
11.8 NetworkServerStats и NetworkClientStats
Доступны через Stats:
| Метрика | Описание |
|---|---|
IncomingReplicationLag | Задержка получения данных от сервера (клиент) |
OutgoingReplicationLag | Задержка отправки данных клиентам (сервер) |
PacketLoss | Доля потерянных пакетов (%) |
BytesPerSecond | Скорость трафика (байт/сек) |
При
PacketLoss > 5%илиLag > 0.2— снижать частоту обновлений, упрощать геометрию.
Часть 12: Заключительная — сводные таблицы, шаблоны, anti-patterns и рекомендации для «Вселенной IT»
Эта часть завершает справочник, объединяя ключевые данные в удобные для справочного использования формы: таблицы лимитов, проверенные шаблоны кода, перечень типичных ошибок и методические рекомендации по включению материала в вашу энциклопедию «Вселенная IT».
12.1 Сводные таблицы: лимиты и характеристики Roblox (2025)
12.1.1 Лимиты DataStore
| Параметр | Значение | Ед. изм. | Комментарий |
|---|---|---|---|
| Чтение (Standard) | 4 000 | /мин/сервер | Всех DataStore суммарно |
| Запись (Standard) | 64 | /мин/сервер | |
| Размер значения | 4 | МБ | Standard DataStore |
| Размер ключа | 50 | символов | UTF-8 |
| OrderedDataStore: запись | 64 | /мин/сервер | Только числовые значения |
| OrderedDataStore: ключей | 1 000 000 | на игру | |
| Время хранения | ∞ | При соблюдении ToS |
12.1.2 Сетевые лимиты
| Параметр | Значение | Ед. изм. | Контекст |
|---|---|---|---|
| Размер пакета Remote | ≤ 1 | МБ | RemoteEvent, RemoteFunction |
| Частота RemoteEvent | ≤ 200 | /сек/игрок | Потеря пакетов при превышении |
| Частота репликации | ~30 | Гц | Адаптивно, зависит от лага |
| Макс. игроков | 100 (стандарт), 200 (Premium) | на сервер | Зависит от подписки |
| Трафик (исходящий) | ≤ 2 | Мбит/сек/сервер | После ~1.5 Мбит/с — деградация |
12.1.3 Производительность
| Подсистема | Норма | Предупреждение | Критично |
|---|---|---|---|
Script (MicroProfiler) | ≤ 1.0 | > 2.0 | > 3.0 мс/кадр |
Physics.StepTime | ≤ 2.0 | > 4.0 | > 5.0 мс/шаг |
Render.Geometry | ≤ 2.0 | > 3.5 | > 4.0 мс/кадр |
Memory.Lua | ≤ 32 | МБ | > 64 МБ — риск OOM |
Instance count | ≤ 5 000 | > 8 000 | > 12 000 — лаги |
12.1.4 Частоты обновления
| Событие | Сервер | Клиент | Примечание |
|---|---|---|---|
RunService.Stepped | 240 Гц | — | Физика, перемещение |
RunService.Heartbeat | — | ~60 Гц | UI, анимации |
Репликация CFrame | — | до 30 Гц | Адаптивно |
RemoteEvent | — | до 200 Гц/игрок | Lossy при перегрузке |
12.2 Проверенные шаблоны кода
12.2.1 Безопасный DataStore с версионированием и fallback
-- ModuleScript: SafeDataStore.lua
local DataStoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local SafeDataStore = {}
SafeDataStore.__index = SafeDataStore
function SafeDataStore.new(name: string, version: number, defaults: table)
local self = setmetatable({
DS = DataStoreService:GetDataStore(name),
Version = version,
Defaults = defaults
}, SafeDataStore)
return self
end
function SafeDataStore:migrate(data: table): table
if not data then return table.clone(self.Defaults) end
if data._v == self.Version then return data end
-- v0 → v1
if not data._v then
data.gold = data.coins or 0
data.coins = nil
data._v = 1
end
-- v1 → v2
if data._v == 1 then
data.resources = { gold = data.gold }
data.gold = nil
data._v = 2
end
data._v = self.Version
return data
end
function SafeDataStore:load(key: string): (table, boolean)
local success, raw = pcall(function()
return self.DS:GetAsync(key)
end)
if not success or not raw then
return table.clone(self.Defaults), false
end
return self:migrate(raw), true
end
function SafeDataStore:save(key: string, data: table, maxRetries: number?)
maxRetries = maxRetries or 3
local delay = 1
data._v = self.Version
for _ = 1, maxRetries do
local success = pcall(function()
self.DS:SetAsync(key, data)
end)
if success then return true end
task.wait(delay)
delay = math.min(delay * 2, 30)
end
return false
end
return SafeDataStore
12.2.2 Типизированный RemoteEvent
-- ModuleScript: TypedRemote.lua
type Decoder = (any) -> (boolean, any)
local TypedRemote = {}
TypedRemote.__index = TypedRemote
function TypedRemote.new(remote: RemoteEvent, decoder: Decoder)
local self = setmetatable({
Remote = remote,
Decoder = decoder
}, TypedRemote)
return self
end
function TypedRemote:FireServer(...)
self.Remote:FireServer(...)
end
function TypedRemote:Connect(callback: (any) -> ())
return self.Remote.OnServerEvent:Connect(function(player, ...)
local ok, data = self.Decoder(...)
if ok then
callback(player, data)
else
warn("Invalid data from", player.Name)
end
end)
end
-- Пример использования:
local PositionDecoder = function(x, y, z)
local pos = Vector3.new(x or 0, y or 0, z or 0)
return pos.Magnitude <= 1000, pos
end
local MoveRemote = TypedRemote.new(
game.ReplicatedStorage.Remotes.MoveTo,
PositionDecoder
)
MoveRemote:Connect(function(player, pos)
-- pos гарантированно валиден
end)
12.2.3 Телепортация с восстановлением состояния
-- Server
local TeleportService = game:GetService("TeleportService")
local function teleportWithState(player, placeId, state)
-- Сохраняем состояние в атрибут (временно)
player:SetAttribute("__TeleportState", state)
-- Телепортируем
TeleportService:Teleport(placeId, {player})
end
-- На целевом сервере:
Players.PlayerAdded:Connect(function(player)
local state = TeleportService:GetPlayerTeleportData(player)
if state then
-- Используем state
else
-- Или из атрибута (если телепортация внутри игры)
state = player:GetAttribute("__TeleportState")
player:SetAttribute("__TeleportState", nil)
end
end)
12.3 Anti-patterns: 20 типичных ошибок и их решение
| № | Ошибка | Последствия | Решение |
|---|---|---|---|
| 1 | player.Character.Humanoid.Health = 100 (клиент) | Подделка HP | Всегда обновлять на сервере |
| 2 | wait() в LocalScript при инициализации | Зависание UI | Использовать :WaitForChild() с таймаутом |
| 3 | RemoteEvent:FireServer(mouse.Hit) | Обман позиции клика | Валидировать расстояние на сервере |
| 4 | game.Players.LocalPlayer в ModuleScript | Ошибка при require() | Передавать player как параметр |
| 5 | :Destroy() в PlayerRemoving без pcall | Потеря данных при краше | Использовать BindToClose + spawn + retry |
| 6 | TextLabel.Text = "HP: " .. humanoid.Health | Спагетти-код | Использовать NumberValue + BindableEvent |
| 7 | for _, v in pairs(table) do ... end на 10k+ | Лаги | Использовать ipairs или for i = 1, #t |
| 8 | Motor6D вместо WeldConstraint для статики | Падение FPS | Заменять на WeldConstraint |
| 9 | Transparency = 0.5 для 1000+ частей | Провал FPS | Использовать 0 или 1 |
| 10 | :GetChildren() в RenderStepped | Пиковая нагрузка | Кэшировать список, обновлять при изменении |
| 11 | RemoteFunction:InvokeClient() для критичных операций | Блокировка сервера | Использовать RemoteEvent + подтверждение |
| 12 | DataStore:SetAsync() при каждом изменении | Превышение квот | Throttling: task.delay(30, ...) |
| 13 | CFrame.new(pos) * CFrame.Angles(...) без нормализации | Накопление ошибок | Использовать CFrame.fromMatrix() или Rotation |
| 14 | :FindFirstChild("Humanoid") без recursive | Пропуск R15/Rthro | Использовать :FindFirstChild("Humanoid", true) |
| 15 | player.UserId в HTTP-запросах | Нарушение GDPR | Хэшировать: HttpService:MD5(userId) |
| 16 | workspace:FindPartOnRay() | Устаревший API | Использовать Raycast() + RaycastParams |
| 17 | game.Lighting.ClockTime = tick() | Некорректное время | Использовать math.clamp(tick() % 86400 / 3600, 0, 24) |
| 18 | :Clone() больших моделей в hot loop | Утечка памяти | Кэшировать клон, использовать Debris |
| 19 | BindableEvent для частой синхронизации | Лаги | Заменять на NumberValue или RemoteEvent |
| 20 | Отсутствие :Disconnect() у соединений | Утечка памяти | Использовать Janitor во всех компонентах |
12.4 Рекомендации для включения в «Вселенную IT»
12.4.1 Структура главы «Roblox (Lua)»
Предлагаемая иерархия (соответствует вашему плану: 5. Языки → Roblox):
5.6 Roblox (Lua)
├── 5.6.1 Введение: среда, ограничения, безопасность
├── 5.6.2 Instance API — базовый класс
├── 5.6.3 Пространственные объекты (Part, Model, CFrame)
├── 5.6.4 Персонаж (Humanoid, Animator, Rig)
├── 5.6.5 Пользовательский интерфейс (GuiObject, CoreGui)
├── 5.6.6 Сетевое взаимодействие (Remote, безопасность)
├── 5.6.7 Сохранение данных (DataStore, OrderedDataStore)
├── 5.6.8 Аудио и визуальные эффекты (Sound, ParticleEmitter)
├── 5.6.9 Интеграции (Marketplace, HttpService, Teleport)
├── 5.6.10 Тестирование и отладка
├── 5.6.11 Производительность и оптимизация
└── 5.6.12 Приложения
├── Таблица лимитов
├── Шаблоны кода
├── Anti-patterns
└── Глоссарий терминов (Rig, CFrame, Replication, DataStore и др.)
12.4.2 Формат подачи
- Для каждой функции/свойства:
#### `Humanoid:MoveTo(position: Vector3, target: Instance = nil) → void`
**Контекст**: Server
**Описание**: Асинхронное движение персонажа к позиции с использованием Pathfinding (при наличии NavMesh).
**Ограничения**:
- Не работает при `Humanoid.Sit = true`
- Останавливается при смене `State` на `Dead`, `Seated`
**События**: `Running`, `Jumping`, `FreeFalling` могут срабатывать в процессе. - Для лимитов — использовать таблицы с единицами измерения и уровнями критичности (как в 12.1).
- Для anti-patterns — формат «Ошибка → Последствия → Решение», как в 12.3.
12.4.3 Перекрёстные ссылки
- Связать с:
- 1.3 Lua — основы языка
- 3.2 Сети — принципы клиент-сервер
- 4.1 Архитектура ПО — паттерны (Observer, Singleton для DataStore)
- 7.1 Инфраструктура — CI/CD для Roblox (Rojo, TestEZ)
12.4.4 Для раздела «Для детей»
- Упрощённая версия:
- Интерактивные примеры через
StarterGui - Визуализация
CFrameчерезPart+Attachment - Игровые аналогии:
RemoteEvent= «радиосвязь»DataStore= «сейф в банке»Humanoid= «робот-помощник»
- Интерактивные примеры через