Перейти к основному содержимому

8.04. Справочник по Roblox

Всем

Справочник по Roblox

Часть 1: Roblox Lua и базовый класс Instance

1.1 Roblox Lua — среда выполнения

  • Версия Lua: 5.1, с рядом ограничений и расширений.

  • Запрещены стандартные библиотеки:

    • os.*, io.*, debug.*, package.*, loadstring
    • collectgarbage — ограничена (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 и др.

  • Типы скриптов:

    ТипВыполняется наДоступ кОсновное назначение
    ScriptServergame.ServerStorage, Workspace, Players, ReplicatedStorage (только чтение в ServerScriptService), DataStoreServiceСерверная логика, безопасные операции
    LocalScriptClientPlayerGui, 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
ИмяТипДоступОписание
NamestringRWИмя экземпляра (не уникальное, но влияет на :FindFirstChild() и .-доступ)
ParentInstance | nilRWРодительский объект. При nil — объект отсоединён (Destroy() удаляет поддерево)
ClassNamestringROИмя класса (например, "Part"). Неизменяемо.
ArchivablebooleanRWУказывает, может ли объект быть сериализован (:Clone(), :GetAttribute(), сохранение в .rbxl). По умолчанию true, но для Player, Humanoid и др. — false.
AncestryChangedRBXScriptSignalROСигнал: (child: Instance, parent: Instance | nil). Вызывается при изменении Parent.
ChangedRBXScriptSignalROСигнал: (propertyName: string). Срабатывает при изменении любого свойства (кроме Parent, Name, ClassName).

⚠️ Свойства Anchored, Position, CFrame, Size, Velocity и др. — не свойства Instance, а специфичны для наследников (например, BasePart).

1.2.2 Методы Instance
МетодПодписьКонтекстОписание
:Clone()→ InstanceBothВозвращает глубокую копию объекта и всех потомков. Объекты с Archivable = false копируются как пустые заглушки (nilInstance.new("Folder")).
:Destroy()→ voidBothУдаляет объект и всех потомков. Вызывает AncestryChanged с parent = nil, затем освобождает память. Безопасен к повторным вызовам.
:FindFirstChild(name: string, recursive: boolean = false)→ Instance | nilBothПоиск дочернего элемента по Name. Если recursive = true — рекурсивно. Не выдаёт ошибку, если не найдено.
:WaitForChild(name: string, timeout: number = ∞)→ InstanceBothБлокирует поток до появления дочернего объекта с указанным именем или таймаута (в секундах). При таймауте — ошибка.
:IsA(className: string)→ booleanBothПроверяет, является ли объект экземпляром указанного класса (учитывает наследование). Эквивалентно typeof(obj) == className для конкретного класса, но работает с иерархией.
:IsDescendantOf(ancestor: Instance)→ booleanBothПроверяет, находится ли объект в поддереве ancestor.
:GetChildren()→ {Instance}BothВозвращает таблицу непосредственных потомков (1 уровень вниз).
:GetDescendants()→ {Instance}BothВозвращает всех потомков (рекурсивно, в порядке обхода "в глубину").
:GetFullName()→ stringBothВозвращает путь от корня, например "Workspace.Part.Script". Полезно для логирования.
:ClearAllChildren()→ voidBothУдаляет всех непосредственных потомков (:Destroy() для каждого).
:GetPropertyChangedSignal(property: string)→ RBXScriptSignalBothВозвращает сигнал, который сработает при изменении указанного свойства. Сигнал: (). Позволяет отслеживать изменения без polling.
:GetAttribute(attributeName: string)→ anyBothПолучает значение пользовательского атрибута (хранится в реплицированной таблице).
:SetAttribute(attributeName: string, value: any)→ voidServerУстанавливает пользовательский атрибут. Поддерживаемые типы: 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
СвойствоТипДоступКонтекстОписание
PositionVector3RWReplicatedПозиция центра масс (в мировых координатах). Устанавливать можно, но предпочтительно — через CFrame.
CFrameCFrameRWReplicatedПолная поза: позиция + ориентация. Основной способ управления положением и поворотом. Изменение CFrame перезаписывает Position и Rotation.
SizeVector3RW (но не во время симуляции, если Anchored = false)ReplicatedРазмер по осям X, Y, Z в стадиях (1 стадия = 1 метр). Минимум — 0.001, 0.001, 0.001.
RotationVector3ROReplicatedЭйлеровы углы (в градусах) относительно мировой системы координат. Вычисляется из CFrame. Изменять напрямую нельзя — только через CFrame.
AnchoredbooleanRWReplicatedЕсли true — объект игнорирует гравитацию и физику, остаётся неподвижным. По умолчанию false.
VelocityVector3RWReplicatedЛинейная скорость центра масс (стадий/сек). Изменяется физикой или вручную.
RotVelocityVector3RWReplicatedУгловая скорость (рад/сек) вокруг осей X, Y, Z в локальной системе.
MassnumberROReplicatedВычисляется как Density × Volume. Зависит от Size и Material.
DensitynumberRWBothПлотность (кг/стадия³). По умолчанию: Wood = 600, Plastic = 1150, Metal = 7850 и др. Изменение Density немедленно меняет Mass.
MaterialEnum.MaterialRWReplicatedОпределяет визуальную текстуру, звук и физические параметры (коэфф. трения, restitution).
ColorColor3RWReplicatedОсновной цвет поверхности (если BrickColor не установлен). Приоритет ниже BrickColor.
BrickColorBrickColorRWReplicatedЦвет из палитры LEGO (999+ значений). Приоритет выше Color.
Transparencynumber (0.0–1.0)RWReplicatedПрозрачность. 0 — непрозрачный, 1 — полностью прозрачный. Влияет на рендер и коллизии (CanCollide).
Reflectancenumber (0.0–1.0)RWReplicatedКоэффициент отражения (0 = матовый, 1 = зеркальный). Только для материалов SmoothPlastic, Neon, Glass, Metal.
CanCollidebooleanRWReplicatedВключение/выключение коллизий с другими BasePart. Если false — проходит сквозь всё (но Touched/TouchEnded не срабатывают).
CanQuerybooleanRWReplicatedУчаствует ли в Workspace:Raycast(), :FindPartsInRegion3() и т.п. По умолчанию true.
CanTouchbooleanRWReplicatedГенерировать ли события Touched/TouchEnded. По умолчанию true. Важно: при false коллизии всё ещё возможны (CanCollide = true).
ShapeEnum.PartTypeROФорма: Block, Ball, Cylinder, CustomMesh, Truss. У Part — читаемое, у MeshPart — всегда CustomMesh.
LockedbooleanRWBothБлокировка в Studio (не влияет на рантайм). Игнорируется в игре.
RootPrioritynumberRW (только для Assembly)ReplicatedПриоритет корня ассемблии (для Humanoid:MoveTo() и IK). Не документирован, используется внутренне.
Методы BasePart
МетодПодписьКонтекстОписание
:GetConnectedParts(recursive: boolean = false)→ {BasePart}BothВозвращает части, соединённые WeldConstraint, RodConstraint, BallSocketConstraint и др. При recursive = true — транзитивное замыкание.
:GetJoints()→ {Constraint}BothВозвращает все Constraint, прикреплённые к части (через Attachment).
:GetMass()→ numberBothТо же, что свойство Mass, но как метод (для совместимости).
:GetRealMass()→ numberBothУчитывает присоединённые Constraint и другие части в ассемблии.
:GetTouchingParts()→ {BasePart}BothВозвращает части, с которыми активно происходит коллизия (контактные точки ≠ 0). Требует CanCollide = true.
:Subtract(part: BasePart)→ UnionOperationServerСоздаёт UnionOperation, выполняющий булево вычитание: self – part.
:Union(parts: {BasePart})→ UnionOperationServerОбъединяет self и parts в UnionOperation.
:Intersect(part: BasePart)→ UnionOperationServerПересечение self ∩ part.
:Resize(extent: Vector3)→ voidServerУвеличивает Size на extent (в стадиях) относительно центра. Устаревший, предпочтительно Size += extent.
:ApplyImpulse(impulse: Vector3)→ voidReplicatedПрикладывает импульс (в кг·стадий/сек) к центру масс. Эффективно Velocity += impulse / Mass.
:ApplyImpulseAtPosition(impulse: Vector3, position: Vector3)→ voidReplicatedПрикладывает импульс в указанной мировой позиции → вызывает вращение.
:ApplyAngularImpulse(impulse: Vector3)→ voidReplicatedПрикладывает угловой импульс (в кг·стадий²/сек) → изменяет RotVelocity.
:GetBoundingBox()→ (Vector3 center, Vector3 size)BothВозвращает осе-выровненный ограничивающий параллелепипед (AABB) части в мировых координатах.
:GetExtents()→ (Vector3 min, Vector3 max)BothТо же, что GetBoundingBox, но возвращаются углы.
:ToWorldSpace(cf: CFrame)→ CFrameBothПреобразует локальную cf (относительно части) в мировую систему координат. Эквивалентно CFrame * cf.
:ToObjectSpace(cf: CFrame)→ CFrameBothОбратное: CFrame:Inverse() * cf.
:GetVelocityAtPosition(position: Vector3)→ Vector3BothЛинейная скорость точки с мировой координатой 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() и т.п. возвращают новый объект типа PartShape = 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
СвойствоТипОписание
PrimaryPartBasePart | nilЧасть, относительно которой определяется поза модели. Если не задана — выбирается первая BasePart.
ArchivablebooleanПо умолчанию 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
СвойствоТипОписание
WorldPositionVector3RO. Мировая позиция точки.
WorldCFrameCFrameRO. Полная мировая поза.
PositionVector3RW. Локальная позиция (относительно ParentBasePart).
RotationVector3RW. Локальный поворот (в градусах, Эйлер).
CFrameCFrameRW. Локальная поза. Приоритет выше Position/Rotation.
AxisVector3RO. Нормализованная ось X WorldCFrame.
SecondaryAxisVector3RO. Нормализованная ось Y WorldCFrame.

Attachment не рендерится, не участвует в коллизиях. Чисто логический объект.


2.4 Пространственные типы данных

Vector3
  • Конструкторы:
    • Vector3.new(x, y, z)
    • Vector3.zeroVector3.new(0, 0, 0)
    • Vector3.oneVector3.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() → identity
    • CFrame.new(x, y, z)
    • CFrame.new(Vector3 pos)
    • CFrame.new(pos, lookAt) → смотрит из pos в lookAt
    • CFrame.new(pos, r00, r01, r02, r10, ..., r22) — прямо матрицу
    • CFrame.Angles(rx, ry, rz) → только поворот (позиция = 0)
    • CFrame.fromMatrix(pos, vX, vY [, vZ]) — из базиса
  • Свойства:
    • .PositionVector3
    • .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 + SizeRegion3.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 \| Blacklist
      • Whitelist: проверяются только объекты из FilterDescendantsInstances
      • Blacklist: проверяются все, кроме указанных
    • .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
СвойствоТипОписание
InstanceBasePartЧасть, в которую попал луч
PositionVector3Точка пересечения
NormalVector3Нормаль поверхности в точке пересечения
DistancenumberРасстояние от origin до Position
MaterialEnum.MaterialМатериал поверхности
FaceEnum.NormalIdГрань блока (Top, Bottom, Front, Back, Left, Right)

2.6 Навигация: PathfindingService

Требует настройки PathfindingLink и NavMesh (в Studio → Pathfinding).

Основные шаги
  1. Получить PathfindingService = game:GetService("PathfindingService")
  2. Создать path = PathfindingService:CreatePath()
  3. Настроить параметры path:
    • AgentRadius, AgentHeight, AgentCanJump, WaypointSpacing
  4. Вызвать path:ComputeAsync(startPos, endPos)yield-функция
  5. Проверить path.Status == Enum.PathStatus.Success
  6. Получить точки: local waypoints = path:GetWaypoints()
PathWaypoint

Таблица:

  • .Position: Vector3
  • .Action: Enum.PathWaypointActionWalk, 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
СвойствоТипДоступКонтекстОписание
HealthnumberRWReplicatedТекущее здоровье. При ≤ 0:TakeDamage(), затем BreakJoints() и Dead = true.
MaxHealthnumberRWReplicatedМаксимум здоровья. По умолчанию 100.
WalkSpeednumberRWReplicatedСкорость ходьбы (стадий/сек). По умолчанию 16. Максимум 100 (ограничение клиента).
JumpPowernumberRWReplicatedИмпульс прыжка (стадий/сек). По умолчанию 50. Максимум 200.
AutoJumpEnabledbooleanRWClientРазрешить автоматический прыжок при столкновении с препятствием (если MoveDirection.Y > 0).
AutoRotatebooleanRWReplicatedПовернуть персонажа в направлении MoveDirection (если true). По умолчанию true.
MoveDirectionVector3RWClientНаправление движения (нормализовано, локальная система координат персонажа). Устанавливается StarterPlayerScripts из ввода.
SitbooleanRWReplicatedЕсли true — персонаж садится (игнорирует гравитацию, MoveDirection игнорируется).
JumpbooleanRWClient → ServerЗапрос прыжка. Устанавливается в true клиентом, сервер реплицирует и применяет физику. Автоматически сбрасывается в false.
PlatformStandbooleanRWReplicatedИгнорировать гравитацию, но оставаться в воздухе (для платформеров).
StateEnum.HumanoidStateTypeROReplicatedТекущее состояние: None, Dead, GettingUp, Jumping, Freefall, FallingDown, Climbing, Seated, PlatformStanding, Swimming, Flying, Running.
RootPartBasePart | nilROReplicatedЧасть, используемая как точка опоры (по умолчанию HumanoidRootPart).
RigTypeEnum.HumanoidRigTypeROR6 (6 частей), R15 (15 частей), Rthro. Задаётся при создании.
DisplayNamestringRWReplicatedОтображаемое имя (не Player.Name).
DisplayDistanceTypeEnum.HumanoidDisplayDistanceTypeRWReplicatedViewer, Subject, None — управляет отображением имени.
BreakJointsOnDeathbooleanRWServerРазрушать ли Motor6D при смерти (по умолчанию true).
HealthDisplayDistancenumberRWReplicatedДистанция, на которой видна полоска здоровья (в стадиях). 0 — всегда, -1 — никогда.
3.2.2 Методы Humanoid
МетодПодписьКонтекстОписание
:TakeDamage(amount: number)→ voidServerНаносит урон. Вызывает событие TakeDamage, затем проверяет Health ≤ 0.
:Heal(amount: number)→ voidServerВосстанавливает здоровье (не выше MaxHealth).
:MoveTo(position: Vector3, target: Instance = nil)→ voidServerАсинхронное движение к позиции. Использует PathfindingService, если доступен. Останавливается при StateRunning.
:Move(direction: Vector3, ignoreAutoRotate: boolean = false)→ voidClientУстанавливает MoveDirection. Устаревший (до 2019), предпочтительно прямая запись в MoveDirection.
:ChangeState(state: Enum.HumanoidStateType)→ booleanServerПринудительно меняет состояние. Возвращает true, если успешно. Опасно — может нарушить консистентность.
:BuildRigFromAttachments(definition: R15HumanoidDescription | nil)→ voidServerПерестраивает скелет по Attachment (требует RigType = R15). Используется для кастомных R15-моделей.
:GetState()→ Enum.HumanoidStateTypeBothТо же, что свойство State.
:GetPlayingAnimationTracks()→ {AnimationTrack}BothВозвращает активные треки анимаций.
:LoadAnimation(animation: Animation)→ AnimationTrackBothЗагружает анимацию для проигрывания. Возвращает AnimationTrack.
:ReplaceFace(faceId: number)→ voidServerМеняет лицо (для R6). Для R15 — используйте FaceCenterAttachment.
:GetAccessories()→ {Accessory}BothВозвращает одетые аксессуары (шляпы и др.).
:RemoveAccessories()→ voidServerСнимает все аксессуары.
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://123456789
      • http://www.roblox.com/asset/?id=123456789
      • rbxasset://anim.rbxl (локально, редко)
3.3.2 AnimationTrack

Результат humanoid:LoadAnimation(animation). Управляет проигрыванием.

Свойства AnimationTrack
СвойствоТипОписание
LengthnumberДлительность анимации (сек).
LoopedbooleanПовторять ли анимацию.
PriorityEnum.AnimationPriorityCore, Movement, Action, Idle. Определяет, какая анимация «побеждает» при конфликте.
Weightnumber (0.0–1.0)Вес смешивания (для слоёв).
IsPlayingbooleanИграет ли трек сейчас.
Методы 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.Attachment0Part1.Attachment1.
  • Свойства:
    • C0, C1 — локальные CFrame для Attachment0 и Attachment1.
    • Transform — дополнительное смещение (устаревшее, не использовать).
  • При Humanoid:BreakJoints() все Motor6D удаляются.

3.6 StarterPlayer и конфигурация персонажа

ОбъектРасположениеНазначение
StarterCharacterStarterPlayerКопируется в Character при спавне. Если пуст — используется стандартный R6/R15.
StarterCharacterScriptsStarterPlayerКопируются в Character. Должны содержать LocalScript для движения и Script для серверной логики.
StarterPlayerScriptsStarterPlayerКопируются в PlayerScripts (дочерний Player). Для клиентской логики вне персонажа.
CharacterAddedPlayerСобытие: (character: Model). Гарантирует, что персонаж загружен.
CharacterRemovingPlayerСобытие: (character: Model). Перед уничтожением.

⚠️ Никогда не обращайтесь к player.Character напрямую — он может быть nil. Всегда используйте:

player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")
-- ...
end)

3.7 Best practices для персонажа

  1. Сервер не должен управлять MoveDirection — это клиентская ответственность. Сервер может корректировать (WalkSpeed = 0 при заморозке), но не задавать направление.
  2. Анимации — клиентская логика. Сервер может запускать AnimationTrack, но только через RemoteEvent (или если анимация влияет на геймплей, например, каст).
  3. Не используйте Humanoid:MoveTo() в реальном времени — только для ИИ или автопилота. Для игрока — MoveDirection.
  4. Сохраняйте Humanoid.Health только на сервере — клиент может подделать Heal().
  5. Для кастомных персонажей:
    • Используйте R15.
    • Создавайте Attachment по стандартным именам (Root, Waist, LeftShoulder и др.).
    • Вызывайте humanoid:BuildRigFromAttachments().

Часть 4: Пользовательский интерфейс (UI) — GuiObject, CoreGui, безопасность и оптимизация

Эта часть описывает графическую подсистему интерфейса Roblox, включая иерархию GuiObject, ограничения рендеринга, безопасность и рекомендации по производительности.
Акцент — на корректное использование StarterGui, изоляцию клиента/сервера и избегание распространённых ошибок.

4.1 Иерархия GuiObject

Все UI-элементы наследуются от GuiObjectInstance:

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
СвойствоТипДоступОписание
SizeUDim2RWРазмер: UDim2.new(scaleX, offsetX, scaleY, offsetY). scale — доля родителя (0.0–1.0), offset — пиксели.
PositionUDim2RWПозиция относительно родителя (левый верхний угол).
AnchorPointVector2RWТочка привязки (0,0 = левый верх, 0.5,0.5 = центр). Смещает Position относительно неё.
AbsoluteSizeVector2ROФактический размер в пикселях.
AbsolutePositionVector2ROФактическая позиция в пикселях.
VisiblebooleanRWОтображение элемента и всех потомков.
ActivebooleanRWОбрабатывать ли ввод (нажатия, наведение). Если false — клики проходят «сквозь».
SelectablebooleanRWМожно ли выбрать элемент (для TextBox, ImageButton).
BackgroundColor3Color3RWЦвет фона.
BackgroundTransparencynumber (0.0–1.0)RWПрозрачность фона. 1 = полностью прозрачный.
BorderColor3Color3RWЦвет границы.
BorderSizePixelnumberRWТолщина границы (в пикселях). 0 = без границы.
ClipsDescendantsbooleanRWОбрезать ли потомков за пределами AbsoluteSize.
ZIndexnumber (1–10)RWГлубина отрисовки (чем выше — тем «ближе»). Максимум 10.
LayoutOrdernumberRWПорядок для UIListLayout, UIGridLayout.
AutoLocalizebooleanRWИспользовать ли систему локализации (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()Клик ЛКМ (вызывается только если MouseButton1DownMouseButton1Up без движения).
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
СвойствоТипОписание
TextstringТекст (поддерживает \n).
TextSizenumberРазмер шрифта (в пикселях). Максимум 100.
FontEnum.FontLegacy, SourceSans, Gotham, RobotoMono и др.
TextColor3Color3Цвет текста.
TextWrappedbooleanПеренос по словам.
TextXAlignment / TextYAlignmentEnum.TextXAlignment / YLeft, Center, Right / Top, Center, Bottom.
RichTextbooleanВключить форматирование: <b>, <i>, <font color="#FF0000">.
TextScaledbooleanАвтомасштабирование текста под размер фрейма.

⚠️ RichText не поддерживает изображения (<img>), только стили.

ImageButton / ImageLabel
СвойствоТипОписание
Imagestringrbxassetid://, http://, rbxasset://.
ImageColor3Color3Цвет наложения (умножение).
ImageTransparencynumberПрозрачность изображения.
ScaleTypeEnum.ScaleTypeStretch, Crop, Fit, Slice (для 9-slice).
SliceCenterRectОбласть для ScaleType.Slice (в пикселях исходного изображения).
ImageRectOffset / ImageRectSizeVector2Для спрайт-листов (смещение и размер кадра).
ScrollingFrame
  • Прокручиваемый контейнер.
  • Требует CanvasSize: UDim2 — размер содержимого (в пикселях: UDim2.new(0, width, 0, height)).
  • Автоматически добавляет скроллбары при ScrollBarThickness > 0.
  • События: CanvasPositionChanged, VerticalScrollBarInset, HorizontalScrollBarInset.
TextBox
СвойствоТипОписание
TextstringТекущий ввод.
ClearTextOnFocusbooleanОчищать при фокусе.
PlaceholderTextstringПодсказка (серый текст).
TextColor3 / PlaceholderColor3Color3Цвет текста / подсказки.
TextEditablebooleanМожно ли редактировать.
MaxLengthnumberОграничение длины (0 = без ограничения).
MultiLinebooleanМногострочный ввод.

⚠️ 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 Безопасность и валидация

  1. UI — клиентская зона ответственности

    • Сервер не должен доверять данным из TextBox.Text, ImageButton.Image, и т.п.
    • Все действия (покупки, ввод команд) должны передаваться через RemoteEvent с валидацией на сервере.
  2. Ограничения ввода

    • TextBox не защищает от XSS — RichText не исполняет JS, но можно обмануть визуально.
    • Для фильтрации чата используйте Chat:FilterStringAsync().
  3. Скрытие 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Отправляет аргументы на сервер.
Клиент.OnClientEventRBXScriptSignalСигнал: (...). Получение данных от сервера.
Сервер:FireAllClients(...)→ voidРассылка всем подключённым игрокам.
Сервер:FireClient(player: Player, ...)→ voidОтправка конкретному игроку.
Сервер.OnServerEventRBXScriptSignalСигнал: (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(...)→ ...Блокирует, возвращает результат с сервера.
Сервер.OnServerInvokefunction(player: Player, ...) → ...Обработчик вызова от клиента.
Сервер:InvokeClient(player: Player, ...)→ any | nilБлокирует, возвращает результат от клиента (или nil при таймауте).
Клиент.OnClientInvokefunction(...) → ...Обработчик вызова от сервера.
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⁵³ теряют точность.
stringUTF-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

  1. Валидация на сервере — обязательно

    • Проверяйте:
      • player ~= nil
      • player.Character ~= nil
      • player == game.Players:GetPlayerFromCharacter(script.Parent) (для Tool)
      • Диапазоны (WalkSpeed ≤ 100, Health ≥ 0)
      • Временные окна (cooldown, double-spend)
  2. Используйте паттерн «запрос-подтверждение» для критичных действий

    -- Клиент: FireServer("buyItem", itemId)
    -- Сервер: проверяет баланс → FireClient(player, "confirmBuy", itemId, price)
    -- Клиент: показывает диалог → FireServer("confirmBuy", itemId)
    -- Сервер: списывает деньги
  3. Не передавайте состояние UI на сервер

    • Например, не отправляйте frame.Visible — сервер должен сам решать, что открыто.
  4. Избегайте FireAllClients для частых обновлений

    • Используйте BindableEvent для локальной синхронизации внутри клиента.
  5. Обрабатывайте ошибки

    • Оборачивайте 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 DataStoreDataStoreService:GetDataStore("Name")Хранение таблиц (до 4 MB на ключ)64 ключа/мин/сервер, 4000 чтений/мин/сервер
OrderedDataStoreDataStoreService:GetOrderedDataStore("Name")Ранжированные данные (числа, до 10⁹)Только number, string (ключ); 64 ключа/мин/сервер
GlobalDataStoreDataStoreService: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Значение (любой сериализуемый тип) или nilisLoaded = false, если ключ не существует.
:SetAsync(key: string, value: any)→ booleantrue при успехе, 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символов
Ключей в OrderedDataStore1 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
СвойствоТипДоступОписание
SoundIdstringRWrbxassetid://, http://, rbxasset://. Поддерживает OGG (рекомендуется), MP3 (устаревшее).
Volumenumber (0.0–10.0)RWГромкость. 1.0 = номинальная. Максимум 10.0 (усиление).
PlaybackSpeednumber (0.1–10.0)RWСкорость воспроизведения (и тональность).
TimePositionnumberRWТекущая позиция (сек). Можно устанавливать для seek.
LoopedbooleanRWПовторять ли звук.
RollOffModeEnum.RollOffModeRWInverse, Linear, Constant. Управляет затуханием по расстоянию.
RollOffMaxDistancenumberRWМакс. дистанция слышимости (стадий). По умолчанию 100.
RollOffMinDistancenumberRWМин. дистанция (внутри неё громкость постоянна).
EmitterSizenumber (0.0–1000.0)RWРазмер источника (для пространственного звука). 0 = точечный.
Pitchnumber (0.5–2.0)RWТолько для совместимости. Используйте PlaybackSpeed.
ArchivablebooleanROВсегда true, если SoundId задан.
7.1.3 Методы Sound
МетодПодписьКонтекстОписание
:Play()→ voidBothЗапуск воспроизведения. Если уже играет — перезапускает.
:Stop()→ voidBothОстановка.
:Pause()→ voidBothПауза (можно продолжить через :Resume()).
:Resume()→ voidBothПродолжение после паузы.
:IsLoaded()→ booleanBothЗагружен ли аудиофайл в память.
:GetInfo()→ tableBothМетаданные: { 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 Свойства
СвойствоТипОписание
Texturestringrbxassetid:// изображение (рекомендуется PNG с альфа-каналом).
EmissionDirectionEnum.NormalIdTop, Bottom, Front, Back, Left, Right, AllFaces.
SpreadAngleVector2Угол рассеяния по X и Y (в градусах).
VelocityInheritancenumber (0.0–1.0)Наследование скорости родителя (0 = игнорировать, 1 = полное).
RatenumberЧастиц/сек. Максимум 1000.
LifetimeNumberRangeДиапазон жизни частицы (сек). Например, NumberRange.new(1, 2).
SizeNumberSequenceИзменение размера во времени.
TransparencyNumberSequenceИзменение прозрачности (0 = непрозрачный, 1 = прозрачный).
ColorColorSequenceИзменение цвета.
RotationNumberRangeНачальный угол поворота.
RotSpeedNumberRangeСкорость вращения (град/сек).
AccelerationVector3Постоянное ускорение (например, гравитация: Vector3.new(0, -196.2, 0)).
DragnumberСопротивление среды.
LockedToPartbooleanДвигать эмиттер вместе с Parent (для Part).
ZOffsetnumberСмещение по 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)
    • Color
    • CastShadows (требует 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

СвойствоТипОписание
Brightnessnumber (0.0–10.0)Общая яркость сцены.
ClockTimenumber (0–24)Время суток (влияет на OutdoorAmbient).
FogStart / FogEndnumberДистанция начала/конца тумана.
FogColorColor3Цвет тумана.
OutdoorAmbientColor3Цвет окружения (небо → объекты).
GlobalShadowsbooleanТени от солнца.
ShadowsbooleanТени от источников света.
TechnologyEnum.TechnologyVoxel, ShadowMap — влияет на качество теней.
EnvironmentDiffuseScale / SpecularScalenumberИнтенсивность отражений.
GeographicLatitudenumber (-90–90)Влияет на положение солнца.
7.5.1 Sky
  • Дочерний Lighting.
  • Свойства:
    • SkyboxBk, Dn, Ft, Lf, Rt, Uprbxassetid:// для граней кубмапы
    • CelestialBodiesShown (Enum.CelestialBody) — Sun, Moon, Both, None
    • StarCount — количество звёзд (до 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 с логикой (например, EnemyEnemyController).
Реализуется через 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: безопасная обработка покупок
  1. Настройка в Creator Dashboard

    • Создать Product → указать Price, Description, Icon.
    • Включить «Process Receipts» → указать Script в ServerScriptService.
  2. Серверный обработчик (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)→ stringGET-запрос. Блокирует.
:PostAsync(url: string, data: string, contentType: Enum.HttpContentType)→ stringPOST-запрос.
: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 сообщений.
messageMessageИнформационное сообщение.
warningWarningПредупреждение.
errorErrorОшибка (не прерывает выполнение).

Каждое сообщение — таблица 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.IncomingKBDoubleВходящий трафик (KB)
Network.OutgoingKBDoubleИсходящий трафик (KB)
Physics.StepTimeDoubleВремя шага физики (мс)
Render.FpsDoubleКадров/сек (только клиент)
Memory.PhysicsInt64Память физики (байт)
Memory.ScriptInt64Память скриптов (байт)
TaskScheduler.ThreadsInt64Количество потоков сервера
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
  1. Запустите игру в Studio.
  2. View → MicroProfiler.
  3. Нажмите Record, выполните сценарий, остановите запись.
  4. Анализируйте:
    • Script — время выполнения Lua (включая yield)
    • Physics — расчёты коллизий, constraints
    • Render — рендеринг геометрии, 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 → MemoryAllocations 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, ReplicationLagNetworkServerStats)
Ошибки DataStoreЛогируйте pcall + err в LogService:error()

🔧 Для CI/CD используйте:

  • Rojo — синхронизация кода между Studio и Git
  • Selene — линтер Lua
  • TestEZ — фреймворк для юнит-тестов
  • Tarmac — форматирование кода

Часть 11: Производительность и оптимизация — углублённый анализ движка Roblox

Эта часть охватывает системные аспекты производительности: профилировку, лимиты, оптимизацию физики, рендеринга, сети и памяти.
Акцент — на измеримые метрики, количественные ограничения и проверенные методы повышения стабильности.


11.1 Архитектура движка и частоты обновления

ПодсистемаЧастотаКонтекстОписание
Render60–240 ГцКлиентЗависит от монитора и RunService:IsHeartbeatStepping(). Не регулируется скриптами.
Heartbeat~60 ГцКлиентRunService.Heartbeat:Connect(...) — для UI, анимаций, предиктивного рендеринга.
RenderStepped~60 ГцКлиентRunService.RenderStepped:Connect(...) — для привязки к кадру (устаревшее, предпочтительно Heartbeat).
Stepped240 ГцСерверRunService.Stepped:Connect(function(time, deltaTime) ...) — физика, перемещение.
Physics240 ГцСервер/КлиентВнутренний шаг симуляции (фиксированный). Не зависит от FPS.
Network20–30 ГцСервер→КлиентЧастота репликации состояния (адаптивная, зависит от NetworkServerStats.OutgoingReplicationLag).

⚠️ SteppedHeartbeat:

  • 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.0LOD, 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 000Rate × Lifetime ≤ 500 на эмиттер
Trail, Beam≤ 50Использовать MinLength, FaceCamera = false для оптимизации

11.5 Сеть: репликация и лимиты

11.5.1 Что реплицируется автоматически
ТипПримерыЧастота
Свойства InstanceCFrame, Velocity, Transparency, Color у BasePartАдаптивно (до 30 Гц)
Value-объектыStringValue, NumberValue, BoolValueПри изменении
SoundTimePosition, PlayingПри изменении
HumanoidHealth, 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)
HeartbeatClientДля UI, анимаций, предиктивных эффектов
RenderSteppedClientУстаревшее. Используйте Heartbeat.
SteppedServerДля физики, перемещения, синхронных расчётов
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.Stepped240 ГцФизика, перемещение
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 типичных ошибок и их решение

ОшибкаПоследствияРешение
1player.Character.Humanoid.Health = 100 (клиент)Подделка HPВсегда обновлять на сервере
2wait() в LocalScript при инициализацииЗависание UIИспользовать :WaitForChild() с таймаутом
3RemoteEvent:FireServer(mouse.Hit)Обман позиции кликаВалидировать расстояние на сервере
4game.Players.LocalPlayer в ModuleScriptОшибка при require()Передавать player как параметр
5:Destroy() в PlayerRemoving без pcallПотеря данных при крашеИспользовать BindToClose + spawn + retry
6TextLabel.Text = "HP: " .. humanoid.HealthСпагетти-кодИспользовать NumberValue + BindableEvent
7for _, v in pairs(table) do ... end на 10k+ЛагиИспользовать ipairs или for i = 1, #t
8Motor6D вместо WeldConstraint для статикиПадение FPSЗаменять на WeldConstraint
9Transparency = 0.5 для 1000+ частейПровал FPSИспользовать 0 или 1
10:GetChildren() в RenderSteppedПиковая нагрузкаКэшировать список, обновлять при изменении
11RemoteFunction:InvokeClient() для критичных операцийБлокировка сервераИспользовать RemoteEvent + подтверждение
12DataStore:SetAsync() при каждом измененииПревышение квотThrottling: task.delay(30, ...)
13CFrame.new(pos) * CFrame.Angles(...) без нормализацииНакопление ошибокИспользовать CFrame.fromMatrix() или Rotation
14:FindFirstChild("Humanoid") без recursiveПропуск R15/RthroИспользовать :FindFirstChild("Humanoid", true)
15player.UserId в HTTP-запросахНарушение GDPRХэшировать: HttpService:MD5(userId)
16workspace:FindPartOnRay()Устаревший APIИспользовать Raycast() + RaycastParams
17game.Lighting.ClockTime = tick()Некорректное времяИспользовать math.clamp(tick() % 86400 / 3600, 0, 24)
18:Clone() больших моделей в hot loopУтечка памятиКэшировать клон, использовать Debris
19BindableEvent для частой синхронизацииЛагиЗаменять на 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 = «робот-помощник»